diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4e48c7bba9..34431799f4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,10 +11,11 @@ /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/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/workflows/docker.yaml b/.github/workflows/docker.yaml index d358de6e98..12ebaddf5b 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -3,6 +3,7 @@ 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 * * *" @@ -12,42 +13,88 @@ jobs: buildx: name: Docker Buildx runs-on: ubuntu-24.04 - environment: dockerhub + 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@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 + uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3 + if: github.event_name != 'pull_request' - name: Set up QEMU - uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3 + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 with: install: true - name: Login to Docker Hub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # 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@9780b0c442fbb1117ed29e0efdff1e18412f7567 # 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 }} + 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:/tmp/element-web-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 + test "$(docker inspect -f {{.State.Running}} $CONTAINER_ID)" == "true" + + # Clean up + docker stop "$CONTAINER_ID" + - name: Docker meta id: meta - uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 + if: github.event_name != 'pull_request' with: images: | vectorim/element-web @@ -60,7 +107,8 @@ jobs: - name: Build and push id: build-and-push - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6 + if: github.event_name != 'pull_request' with: context: . push: true @@ -72,6 +120,7 @@ jobs: 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 @@ -81,6 +130,7 @@ jobs: - name: Update repo description uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4 + if: github.event_name != 'pull_request' continue-on-error: true with: username: ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/.github/workflows/playwright-image-updates.yaml b/.github/workflows/playwright-image-updates.yaml index e5e2f739c0..7681600dde 100644 --- a/.github/workflows/playwright-image-updates.yaml +++ b/.github/workflows/playwright-image-updates.yaml @@ -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/release.yml b/.github/workflows/release.yml index 22739da21f..78383e8bf5 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 }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fae1fb74c1..710e996b39 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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@119b3320db3f04d89e91df840844b92d57ce3468 + uses: guibranco/github-status-action-v2@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc with: authToken: ${{ secrets.GITHUB_TOKEN }} state: success diff --git a/.github/workflows/update-jitsi.yml b/.github/workflows/update-jitsi.yml index a3abcb002f..f4fd13892b 100644 --- a/.github/workflows/update-jitsi.yml +++ b/.github/workflows/update-jitsi.yml @@ -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/CHANGELOG.md b/CHANGELOG.md index 2de3ce4d9d..792fdd876c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +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 diff --git a/Dockerfile b/Dockerfile index 93d7c676d9..ed1312101e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +# syntax=docker.io/docker/dockerfile:1.14-labs + # Builder FROM --platform=$BUILDPLATFORM node:22-bullseye AS builder @@ -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 @@ -19,11 +21,15 @@ RUN cp /src/config.sample.json /src/webapp/config.json # App FROM nginx:alpine-slim +# 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/ +COPY /docker/docker-entrypoint.d/* /docker-entrypoint.d/ # 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 @@ -40,3 +46,5 @@ USER nginx # HTTP listen port ENV ELEMENT_WEB_PORT=80 + +HEALTHCHECK --start-period=5s CMD wget --retry-connrefused --tries=5 -q --wait=3 --spider http://localhost:$ELEMENT_WEB_PORT/config.json diff --git a/babel.config.js b/babel.config.js index b63a90e5ff..58df067b79 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/docker/docker-entrypoint.d/18-load-element-modules.sh b/docker/docker-entrypoint.d/18-load-element-modules.sh new file mode 100755 index 0000000000..14a3b53531 --- /dev/null +++ b/docker/docker-entrypoint.d/18-load-element-modules.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# Loads modules from `/tmp/element-web-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 "/tmp/element-web-modules" ]; then + cd /tmp/element-web-modules + + for MODULE in * + do + # If the module has a package.json, use its main field as the entrypoint + ENTRYPOINT="index.js" + if [ -f "/tmp/element-web-modules/$MODULE/package.json" ]; then + ENTRYPOINT=$(jq -r '.main' "/tmp/element-web-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 06f33e08dd..53870038b6 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 /tmp/element-web-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 0000000000..9dfb8e4776 --- /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 + ... + } + ``` +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 = (props: IProps) => { + const vm = useFooViewModel(); + .... + return( +
+ {vm.somethingUseful} +
+ ); + } + ``` +3. Views are also allowed to accept the view model as a prop, eg: + ```tsx + const FooView: React.FC = ({ vm }: IProps) => { + .... + return( +
+ {vm.somethingUseful} +
+ ); + } + ``` +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/config.md b/docs/config.md index 47d148247f..9ae9f21254 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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 diff --git a/docs/install.md b/docs/install.md index f6bd98611c..743ae0a93a 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 `/tmp/element-web-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/element-web-config/ +- /etc/nginx/conf.d/ + The behaviour of the docker image can be customised via the following environment variables: diff --git a/knip.ts b/knip.ts index 17ad531332..0188e096e5 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", ], diff --git a/package.json b/package.json index 8558639470..385005b03f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.91", + "version": "1.11.94", "description": "Element: the future of secure communication", "author": "New Vector Ltd.", "repository": { @@ -74,7 +74,7 @@ "@types/react-dom": "18.3.5", "oidc-client-ts": "3.1.0", "jwt-decode": "4.0.0", - "caniuse-lite": "1.0.30001697", + "caniuse-lite": "1.0.30001701", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0", "wrap-ansi": "npm:wrap-ansi@^7.0.0" }, @@ -88,12 +88,12 @@ "@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": "^3.0.0", - "@vector-im/compound-web": "^7.6.1", - "@vector-im/matrix-wysiwyg": "2.38.0", + "@vector-im/compound-design-tokens": "^4.0.0", + "@vector-im/compound-web": "^7.6.4", + "@vector-im/matrix-wysiwyg": "2.38.2", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-en": "^3.0.2", @@ -138,7 +138,7 @@ "png-chunks-extract": "^1.0.0", "posthog-js": "1.157.2", "qrcode": "1.5.4", - "re-resizable": "6.10.3", + "re-resizable": "6.11.2", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.0", "react-blurhash": "^0.3.0", @@ -162,9 +162,11 @@ "@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", @@ -218,12 +220,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", @@ -274,7 +276,7 @@ "postcss-preset-env": "^10.0.0", "postcss-scss": "^4.0.4", "postcss-simple-vars": "^7.0.1", - "prettier": "3.4.2", + "prettier": "3.5.2", "process": "^0.11.10", "raw-loader": "^4.0.2", "rimraf": "^6.0.0", @@ -288,7 +290,7 @@ "terser-webpack-plugin": "^5.3.9", "testcontainers": "^10.16.0", "ts-node": "^10.9.1", - "typescript": "5.7.3", + "typescript": "5.8.2", "util": "^0.12.5", "web-streams-polyfill": "^4.0.0", "webpack": "^5.89.0", diff --git a/playwright/Dockerfile b/playwright/Dockerfile index 7e918e04f7..26ab7e5603 100644 --- a/playwright/Dockerfile +++ b/playwright/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.49.1-noble +FROM mcr.microsoft.com/playwright:v1.50.1-noble WORKDIR /work diff --git a/playwright/e2e/crypto/crypto.spec.ts b/playwright/e2e/crypto/crypto.spec.ts index ace1b74c74..2d294ff7c2 100644 --- a/playwright/e2e/crypto/crypto.spec.ts +++ b/playwright/e2e/crypto/crypto.spec.ts @@ -28,7 +28,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( diff --git a/playwright/e2e/crypto/dehydration.spec.ts b/playwright/e2e/crypto/dehydration.spec.ts index 472ee39493..89ee854c91 100644 --- a/playwright/e2e/crypto/dehydration.spec.ts +++ b/playwright/e2e/crypto/dehydration.spec.ts @@ -22,18 +22,6 @@ 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", () => { @@ -116,6 +104,40 @@ test.describe("Dehydration", () => { expect(dehydratedDeviceIds.length).toBe(1); expect(dehydratedDeviceIds[0]).not.toEqual(initialDehydratedDeviceIds[0]); }); + + 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 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 expectDehydratedDeviceEnabled(app); + + // 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 expectDehydratedDeviceDisabled(app); + }); }); async function getDehydratedDeviceIds(client: Client): Promise { @@ -144,3 +166,16 @@ async function expectDehydratedDeviceEnabled(app: ElementAppPage): Promise }) .toEqual(1); } + +/** Wait for our user to not have a dehydrated device */ +async function expectDehydratedDeviceDisabled(app: ElementAppPage): Promise { + // 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 ee710d1cfb..9be79452c4 100644 --- a/playwright/e2e/crypto/device-verification.spec.ts +++ b/playwright/e2e/crypto/device-verification.spec.ts @@ -21,6 +21,7 @@ import { waitForVerificationRequest, } from "./utils"; import { type Bot } from "../../pages/bot"; +import { Toasts } from "../../pages/toasts.ts"; test.describe("Device verification", { tag: "@no-webkit" }, () => { let aliceBotClient: Bot; @@ -72,6 +73,51 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { 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 }) => { // A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key" await logIntoElement(page, credentials); diff --git a/playwright/e2e/forgot-password/forgot-password.spec.ts b/playwright/e2e/forgot-password/forgot-password.spec.ts index 14d1a816c7..d075afda73 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 { type Credentials } from "../../plugins/homeserver"; const email = "user@nowhere.dummy"; -const test = base.extend<{ credentials: Pick }>({ +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 73238f8c3d..8d64e6e047 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/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 0000000000..daa8d3869f --- /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-view/room-list-view.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts similarity index 78% rename from playwright/e2e/left-panel/room-list-view/room-list-view.spec.ts rename to playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts index 7cd5122e8a..53795773ed 100644 --- a/playwright/e2e/left-panel/room-list-view/room-list-view.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts @@ -9,7 +9,7 @@ import { type Page } from "@playwright/test"; import { test, expect } from "../../../element-web-test"; -test.describe("Search section of the room list", () => { +test.describe("Room list panel", () => { test.use({ labsFlags: ["feature_new_room_list"], }); @@ -19,7 +19,7 @@ test.describe("Search section of the room list", () => { * @param page */ function getRoomListView(page: Page) { - return page.getByTestId("room-list-view"); + return page.getByTestId("room-list-panel"); } test.beforeEach(async ({ page, app, user }) => { @@ -27,8 +27,8 @@ test.describe("Search section of the room list", () => { await app.closeNotificationToast(); }); - test("should render the room list view", { tag: "@screenshot" }, async ({ page, app, user }) => { + test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => { const roomListView = getRoomListView(page); - await expect(roomListView).toMatchScreenshot("room-list-view.png"); + await expect(roomListView).toMatchScreenshot("room-list-panel.png"); }); }); diff --git a/playwright/e2e/left-panel/room-list-view/room-list-search.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts similarity index 100% rename from playwright/e2e/left-panel/room-list-view/room-list-search.spec.ts rename to playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts diff --git a/playwright/e2e/room/room-header.spec.ts b/playwright/e2e/room/room-header.spec.ts index 790d97545c..78e37cd4d2 100644 --- a/playwright/e2e/room/room-header.spec.ts +++ b/playwright/e2e/room/room-header.spec.ts @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import { type Page } from "@playwright/test"; +import { type Visibility } from "matrix-js-sdk/src/matrix"; import { test, expect } from "../../element-web-test"; import { type ElementAppPage } from "../../pages/ElementAppPage"; @@ -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/security-user-settings-tab.spec.ts b/playwright/e2e/settings/security-user-settings-tab.spec.ts index 9b9439796d..9a5616ef59 100644 --- a/playwright/e2e/settings/security-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/security-user-settings-tab.spec.ts @@ -32,7 +32,7 @@ test.describe("Security user settings tab", () => { }); 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( @@ -41,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 enable show integrations as enabled", async ({ app, page, user }) => { const tab = await app.settings.openUserSettings("Security"); const setIntegrationManager = tab.locator(".mx_SetIntegrationManager"); diff --git a/playwright/e2e/timeline/timeline.spec.ts b/playwright/e2e/timeline/timeline.spec.ts index 8f4af33da8..5c1f99511e 100644 --- a/playwright/e2e/timeline/timeline.spec.ts +++ b/playwright/e2e/timeline/timeline.spec.ts @@ -875,6 +875,40 @@ 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.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => { 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 6524a45a67..b0bb64273e 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/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 ffe1b0be50..bc9d6c88c3 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/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 e3c37e79c9..be47bf9e44 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 377e1931be..8bc25d6f2f 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 ec7b9a174d..5537ae9991 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 c66606efcd..7e3b73aaee 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 02cbff0e3f..e8fbfdce04 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 fa3cf90430..9bc23433a5 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-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 0000000000..8382d3b184 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 0000000000..6c871b6b9c 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 0000000000..d2934c2a76 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 0000000000..bdb7f8fa2a 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 0000000000..d03dbf992b 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-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 0000000000..123cf37586 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-view/room-list-search.spec.ts/search-section-linux.png b/playwright/snapshots/left-panel/room-list-view/room-list-search.spec.ts/search-section-linux.png deleted file mode 100644 index 6c2684e849..0000000000 Binary files a/playwright/snapshots/left-panel/room-list-view/room-list-search.spec.ts/search-section-linux.png and /dev/null differ diff --git a/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png b/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png deleted file mode 100644 index f114eff64c..0000000000 Binary files a/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png and /dev/null 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 a4b7d0a992..0e65830b51 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/permalinks/permalinks.spec.ts/permalink-rendering-linux.png b/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png index 9159846c3f..b386eaa564 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/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 a842b686dd..0a4abba833 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 d9d12951df..f00047fe84 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 4b724d7772..fc52c2cb65 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 f78a2a0b16..dd458f3157 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 da924bae82..16b686e101 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/right-panel/file-panel.spec.ts/empty-linux.png b/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png index 0566a21175..c1fced1938 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/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 0000000000..471c26ccdb 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 12fd5c79d3..099e23bd6b 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/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 a8b3ae3ea7..d1c320bff1 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 5307b7400a..cc3c32ee95 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 eaa68eae4a..6e0cc3e06f 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 036ff61851..8e29fd26b8 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 147fcfa057..60678f3bb4 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 5475f9a537..cc2dc4a0a7 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 23b88c022c..068aa2901f 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 6378098d7a..eb249d0b24 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 f2269a0532..3b221183fb 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 6b41f30acd..73f7f02d65 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 index ab50f21a56..51c0bd8740 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/encryption-details-linux.png 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 index a7df3804dc..c8b3bb6f17 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/reset-cryptographic-identity-linux.png 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 index d14cff8071..ebcab00f75 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png 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/out-of-sync-recovery-linux.png b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png index f1a9152e89..ba4ccfe18e 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png 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 index 643fe46a1d..dcc1f25008 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png 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 b2083a5dd4..8d6f2a74f4 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 0294549459..e8def33311 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 8a0f80fcea..fb778886c6 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/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 1a413094ae..a1f286ac73 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 099c0c549e..0d8aa341c0 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 6cc32cc431..00a96ce522 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 a3f014d066..d34640d213 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/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 c08a36a808..833dc3a8c7 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 62a8c5b8d1..164c1a8a08 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/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 720cc41548..748bdd5c21 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/invite-teammates-dialog-linux.png b/playwright/snapshots/spaces/spaces.spec.ts/invite-teammates-dialog-linux.png index 2244dc7cf9..b69496e5fe 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/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 301201c1f6..f217fe7094 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 67414d1e7b..30fa37ab9e 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 4b779aa788..4bad759050 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/timeline.spec.ts/code-block-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/code-block-linux.png new file mode 100644 index 0000000000..2f62d0dec6 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/edited-code-block-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/edited-code-block-linux.png new file mode 100644 index 0000000000..46f5cf1a7a 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 97bd5566b7..8d3d7b09ed 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/expanded-gels-and-messages-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png index 8193983d5b..f6840e8daf 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 bdb815daf0..6154a0a268 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 9176352ede..06853769d7 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 97bd5566b7..8d3d7b09ed 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-redaction-placeholder-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png index 1ba4712621..9d9dacd1bf 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/user-menu/user-menu.spec.ts/user-menu-linux.png b/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png index e2c6718a93..73c9ce49ac 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 bd47484596..dc832f31c9 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/synapse.ts b/playwright/testcontainers/synapse.ts index 94ecb557a4..aecee70180 100644 --- a/playwright/testcontainers/synapse.ts +++ b/playwright/testcontainers/synapse.ts @@ -25,7 +25,7 @@ import { type HomeserverContainer, type StartedHomeserverContainer } from "./Hom import { type StartedMatrixAuthenticationServiceContainer } from "./mas.ts"; import { Api, ClientServerApi, type Verb } from "../plugins/utils/api.ts"; -const TAG = "develop@sha256:dfacd4d40994c77eb478fc5773913a38fbf07d593421a5410c5dafb8330ddd13"; +const TAG = "develop@sha256:bae06837a9ab79f939bf27cdb94ce10ebc34a176c975e77f6aa306b20e7d4076"; const DEFAULT_CONFIG = { server_name: "localhost", @@ -144,6 +144,7 @@ const DEFAULT_CONFIG = { enabled: true, include_offline_users_on_sync: true, }, + room_list_publication_rules: [{ action: "allow" }], }; export type SynapseConfig = Partial; diff --git a/res/css/_common.pcss b/res/css/_common.pcss index fe8eff2286..75180013f6 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -589,18 +589,21 @@ 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_UserProfileSettings button, + .mx_ShareDialog button, + .mx_UnpinAllDialog button, + .mx_ThemeChoicePanel_CustomTheme button, + .mx_Dialog_nonDialogButton, + .mx_AccessibleButton, + .mx_IdentityServerPicker 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 +619,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 +668,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 e6ca0c7ae9..cd260ca3aa 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -269,8 +269,9 @@ @import "./views/right_panel/_VerificationPanel.pcss"; @import "./views/right_panel/_WidgetCard.pcss"; @import "./views/room_settings/_AliasSettings.pcss"; -@import "./views/rooms/RoomListView/_RoomListSearch.pcss"; -@import "./views/rooms/RoomListView/_RoomListView.pcss"; +@import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss"; +@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss"; +@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss"; @import "./views/rooms/_AppsDrawer.pcss"; @import "./views/rooms/_Autocomplete.pcss"; @import "./views/rooms/_AuxPanel.pcss"; @@ -288,6 +289,8 @@ @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"; @@ -311,8 +314,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"; @@ -348,7 +349,6 @@ @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"; @@ -360,6 +360,7 @@ @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"; diff --git a/res/css/structures/ErrorView.pcss b/res/css/structures/ErrorView.pcss index cf09ac02af..ddc510e188 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/_LeftPanel.pcss b/res/css/structures/_LeftPanel.pcss index cf2845b173..c76fd5da02 100644 --- a/res/css/structures/_LeftPanel.pcss +++ b/res/css/structures/_LeftPanel.pcss @@ -113,7 +113,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 +180,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/_SpaceHierarchy.pcss b/res/css/structures/_SpaceHierarchy.pcss index 31dad9413f..02f39a0b72 100644 --- a/res/css/structures/_SpaceHierarchy.pcss +++ b/res/css/structures/_SpaceHierarchy.pcss @@ -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; } diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index 31dab1ee8d..664d781829 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -365,7 +365,8 @@ 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, diff --git a/res/css/structures/_SplashPage.pcss b/res/css/structures/_SplashPage.pcss index 6f976ba575..26eec65a93 100644 --- a/res/css/structures/_SplashPage.pcss +++ b/res/css/structures/_SplashPage.pcss @@ -16,7 +16,8 @@ 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% diff --git a/res/css/views/context_menus/_MessageContextMenu.pcss b/res/css/views/context_menus/_MessageContextMenu.pcss index f365c4a293..9fc454f328 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/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss index da71b4462b..83b9fe96b4 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss @@ -21,7 +21,7 @@ Please see LICENSE files in the repository root for full details. &.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-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); background-size: 24px; background-color: transparent; } @@ -120,7 +120,7 @@ Please see LICENSE files in the repository root for full details. width: 16px; left: 0; top: 2px; /* alignment */ - 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: contain; } diff --git a/res/css/views/elements/_InfoTooltip.pcss b/res/css/views/elements/_InfoTooltip.pcss index 5229b7d9f5..a214f0bf83 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/rooms/RoomListPanel/_RoomListHeaderView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss new file mode 100644 index 0000000000..8ce4655e58 --- /dev/null +++ b/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss @@ -0,0 +1,39 @@ +/* + * 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 { + height: 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; + } + } + + button { + color: var(--cpd-color-icon-secondary); + } + + .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/RoomListView/_RoomListView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss similarity index 94% rename from res/css/views/rooms/RoomListView/_RoomListView.pcss rename to res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss index 117810e6b7..eb1f6e5fe5 100644 --- a/res/css/views/rooms/RoomListView/_RoomListView.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -.mx_RoomListView { +.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/RoomListView/_RoomListSearch.pcss b/res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss similarity index 87% rename from res/css/views/rooms/RoomListView/_RoomListSearch.pcss rename to res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss index 55b1def58a..f175ab3976 100644 --- a/res/css/views/rooms/RoomListView/_RoomListSearch.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss @@ -9,7 +9,7 @@ /* From figma, this should be aligned with the room header */ height: 64px; box-sizing: border-box; - border-bottom: 1px solid var(--cpd-color-bg-subtle-primary); + border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary); padding: 0 var(--cpd-space-3x); svg { @@ -31,7 +31,7 @@ } } - .mx_RoomListSearch_explore:hover { + .mx_RoomListSearch_button:hover { svg { fill: var(--cpd-color-icon-primary); } diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index 35097fb3b8..54f91bd5f4 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -683,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 { 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 74e2e86ed1..acf162b7a2 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 396aa4a61a..c04b56d94a 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/_MemberTileView.pcss b/res/css/views/rooms/_MemberTileView.pcss index 21c4bc1622..307625d042 100644 --- a/res/css/views/rooms/_MemberTileView.pcss +++ b/res/css/views/rooms/_MemberTileView.pcss @@ -27,13 +27,11 @@ 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_userLabel { font: var(--cpd-font-body-sm-regular); - font-size: 13px; color: var(--cpd-color-text-secondary); margin-left: var(--cpd-space-4x); } diff --git a/res/css/views/rooms/_RoomHeader.pcss b/res/css/views/rooms/_RoomHeader.pcss index ac2e419e0d..afde8f3464 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 { diff --git a/res/css/views/settings/_SetIdServer.pcss b/res/css/views/settings/_SetIdServer.pcss deleted file mode 100644 index 377292451f..0000000000 --- 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/encryption/_ChangeRecoveryKey.pcss b/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss index d657743140..ceacb22c27 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 f125aea176..87118135e5 100644 --- a/res/css/views/settings/encryption/_EncryptionCard.pcss +++ b/res/css/views/settings/encryption/_EncryptionCard.pcss @@ -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 0000000000..6b18fcff65 --- /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/_ResetIdentityPanel.pcss b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss index e4e05638ce..8318d6d91c 100644 --- a/res/css/views/settings/encryption/_ResetIdentityPanel.pcss +++ b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss @@ -5,22 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -.mx_ResetIdentityPanel { - .mx_ResetIdentityPanel_content { - display: flex; - flex-direction: column; - gap: var(--cpd-space-3x); - - > span { - font: var(--cpd-font-body-md-medium); - text-align: center; - } - } - - .mx_ResetIdentityPanel_footer { - display: flex; - flex-direction: column; - gap: var(--cpd-space-4x); - justify-content: center; - } +// Red text for the "Do not close this window" warning +.mx_ResetIdentityPanel_warning { + color: var(--cpd-color-text-critical-primary); } diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss index 2d3ea2e4f4..c9c86717e6 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 32ca7d3d1a..eea7197d9f 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/css/_light.pcss b/res/themes/light/css/_light.pcss index 1a1705a9c1..a6bb29bac4 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/src/AddThreepid.ts b/src/AddThreepid.ts index 8924399886..7d2c4cefb5 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -16,7 +16,6 @@ import { MatrixError, HTTPError, type IThreepid, - type UIAResponse, } from "matrix-js-sdk/src/matrix"; import Modal from "./Modal"; @@ -181,9 +180,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 | Error | null] - > { + public async checkEmailLinkClicked(): Promise<[success?: boolean, result?: IAddThreePidOnlyBody | Error | null]> { try { if (this.bind) { const authClient = new IdentityAuthClient(); @@ -270,7 +267,7 @@ export default class AddThreepid { */ public async haveMsisdnToken( msisdnToken: string, - ): Promise<[success?: boolean, result?: UIAResponse | Error | null]> { + ): Promise<[success?: boolean, result?: IAddThreePidOnlyBody | Error | null]> { const authClient = new IdentityAuthClient(); if (this.submitUrl) { diff --git a/src/CreateCrossSigning.ts b/src/CreateCrossSigning.ts index cb1eb528af..db9bc3e3fe 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 { type AuthDict, type MatrixClient, MatrixError, type 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"; @@ -38,7 +38,7 @@ export async function createCrossSigning(cli: MatrixClient): Promise { export async function uiAuthCallback( matrixClient: MatrixClient, - makeRequest: (authData: AuthDict) => Promise>, + makeRequest: (authData: AuthDict) => Promise, ): Promise { try { await makeRequest({}); diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index b779952a86..cf46be41fa 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -15,9 +15,10 @@ import { type SyncState, ClientStoppedError, } from "matrix-js-sdk/src/matrix"; -import { logger as baseLogger } from "matrix-js-sdk/src/logger"; +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"; @@ -48,6 +49,11 @@ 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. +const BACKUP_DISABLED_ACCOUNT_DATA_KEY = "m.org.matrix.custom.backup_disabled"; + const logger = baseLogger.getChild("DeviceListener:"); export default class DeviceListener { @@ -91,6 +97,7 @@ export default class DeviceListener { 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); @@ -113,6 +120,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); @@ -220,6 +228,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. * @@ -268,18 +281,29 @@ export default class DeviceListener { private async doRecheck(): Promise { 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(); @@ -301,6 +325,7 @@ export default class DeviceListener { await this.reportCryptoSessionStateToAnalytics(cli); if (this.dismissedThisDeviceToast || allSystemsReady) { + logSpan.info("No toast needed"); hideSetupEncryptionToast(); this.checkKeyBackupStatus(); @@ -311,25 +336,33 @@ 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. - logger.info("Cross-signing not ready: showing SET_UP_ENCRYPTION toast"); + 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 - logger.info("Current device not verified: showing VERIFY_THIS_SESSION toast"); + 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. - logger.info("Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast"); + logSpan.info( + "Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast", + crossSigningStatus.privateKeysCachedLocally, + ); showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC); } else if (defaultKeyId === null) { - // the user just hasn't set up 4S yet: prompt them to do so - logger.info("No default 4S key: showing SET_UP_RECOVERY toast"); - showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY); + // the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to key storage) + const disabledEvent = cli.getAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY); + if (!disabledEvent?.getContent().disabled) { + 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"); + } } 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, @@ -338,6 +371,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 @@ -370,9 +405,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(); @@ -397,7 +432,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); } } diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index 2eebd9c760..67b7515eac 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -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: { diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index 33d3f98683..a41b890b19 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -59,7 +59,7 @@ export const DEFAULTS: DeepReadonly = { 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/async-components/structures/ErrorView.tsx b/src/async-components/structures/ErrorView.tsx index 90c03c12dd..57cf048403 100644 --- a/src/async-components/structures/ErrorView.tsx +++ b/src/async-components/structures/ErrorView.tsx @@ -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 ( {macOsUrl && ( @@ -92,12 +92,12 @@ const DesktopAppLinks: React.FC<{ )} {win64Url && ( )} - {win32Url && ( - )} {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<{ diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index daefa267bc..2bcf577d9f 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details. import React, { createRef } from "react"; import FileSaver from "file-saver"; import { logger } from "matrix-js-sdk/src/logger"; -import { type AuthDict, type UIAResponse } from "matrix-js-sdk/src/matrix"; +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"; @@ -177,9 +177,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent Promise>, - ): Promise => { + private doBootstrapUIAuth = async (makeRequest: (authData: AuthDict) => Promise): Promise => { const dialogAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { title: _t("auth|uia|sso_title"), diff --git a/src/components/structures/HomePage.tsx b/src/components/structures/HomePage.tsx index 720a78cf62..01c0042752 100644 --- a/src/components/structures/HomePage.tsx +++ b/src/components/structures/HomePage.tsx @@ -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/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index f9c389679c..d1c670c27c 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -27,13 +27,10 @@ import Spinner from "../views/elements/Spinner"; export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); -type InteractiveAuthCallbackSuccess = ( - success: true, - response: T, - extra?: { emailSid?: string; clientSecret?: string }, -) => Promise; -type InteractiveAuthCallbackFailure = (success: false, response: IAuthData | Error) => Promise; -export type InteractiveAuthCallback = InteractiveAuthCallbackSuccess & InteractiveAuthCallbackFailure; +export type InteractiveAuthCallback = { + (success: true, response: T, extra?: { emailSid?: string; clientSecret?: string }): Promise; + (success: false, response: IAuthData | Error): Promise; +}; export interface InteractiveAuthProps { // matrix client to use for UI auth requests @@ -49,10 +46,6 @@ export interface InteractiveAuthProps { 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 extends React.Component { return (
- +
); @@ -415,7 +415,7 @@ export default class LeftPanel extends React.Component {
{shouldShowComponent(UIComponent.FilterContainer) && this.renderSearchDialExplore()} {this.renderBreadcrumbs()} - {!this.props.isMinimized && } + {!this.props.isMinimized && }

Or use our mobile app

diff --git a/test/unit-tests/components/structures/MainSplit-test.tsx b/test/unit-tests/components/structures/MainSplit-test.tsx index 1b9501ee27..2feb9b22f6 100644 --- a/test/unit-tests/components/structures/MainSplit-test.tsx +++ b/test/unit-tests/components/structures/MainSplit-test.tsx @@ -86,7 +86,7 @@ describe("", () => { 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/__snapshots__/FilePanel-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap index 6a58ac9811..d8a1e2302d 100644 --- a/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap +++ b/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap @@ -12,7 +12,7 @@ exports[`FilePanel renders empty state 1`] = ` class="mx_BaseCard_header_title" >

Files @@ -20,14 +20,14 @@ exports[`FilePanel renders empty state 1`] = `

@@ -263,13 +263,13 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
@@ -563,13 +563,13 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
@@ -940,13 +940,13 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
@@ -1325,13 +1325,13 @@ exports[`RoomView should not display the timeline when the room encryption is lo
0
@@ -1497,7 +1497,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
0
@@ -1878,7 +1878,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
0
@@ -2025,7 +2025,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = ` class="mx_BaseCard_header_title" >

Chat @@ -2033,14 +2033,14 @@ exports[`RoomView video rooms should render joined video room view 1`] = `

@@ -41,7 +41,7 @@ exports[` renders sidebar correctly with beacons 1`] = ` class="mx_BeaconListItem" > renders sidebar correctly without beacons 1`] = ` xmlns="http://www.w3.org/2000/svg" >
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 bf0fc31443..d48b62c57d 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" > should list spaces which are not par
Make sure that you really want to remove all pinned messages. This action can’t be undone. @@ -32,7 +32,7 @@ exports[` should render 1`] = ` class="mx_UnpinAllDialog_buttons" >
@@ -161,7 +161,7 @@ exports[`AppTile for a pinned widget should render 1`] = ` xmlns="http://www.w3.org/2000/svg" >
@@ -182,7 +182,7 @@ exports[`AppTile for a pinned widget should render 1`] = ` xmlns="http://www.w3.org/2000/svg" >
@@ -213,7 +213,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
@@ -273,7 +273,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = ` xmlns="http://www.w3.org/2000/svg" >
@@ -294,7 +294,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = ` xmlns="http://www.w3.org/2000/svg" >
@@ -316,7 +316,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
@@ -404,7 +404,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
@@ -464,7 +464,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = ` xmlns="http://www.w3.org/2000/svg" >
@@ -485,7 +485,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = ` xmlns="http://www.w3.org/2000/svg" >
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 2956bed066..2e923be862 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 @@ -9,10 +9,10 @@ exports[` renders with a tooltip 1`] = ` tabindex="0" >
renders dropdown options in menu 1`] = ` xmlns="http://www.w3.org/2000/svg" > should render the expected pill for @room 1`] = ` >
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 238097d995..f0c4749153 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[` applies class when isMinimised is truthy 1`] = ` xmlns="http://www.w3.org/2000/svg" >

renders correctly for MapStyleUrlNotConfigured 1`] = ` xmlns="http://www.w3.org/2000/svg" >

renders correctly for MapStyleUrlNotReachable 1`] = ` xmlns="http://www.w3.org/2000/svg" >

renders with location icon when no room member 1`] = ` xmlns="http://www.w3.org/2000/svg" >

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 f2b3e4cc8b..f1663bb751 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[` creates a marker on mount 1`] = ` xmlns="http://www.w3.org/2000/svg" >
@@ -45,7 +45,7 @@ exports[` removes marker on unmount 1`] = ` xmlns="http://www.w3.org/2000/svg" >
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 4aa18b2f1e..02131a43d7 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[` renders buttons 1`] = ` xmlns="http://www.w3.org/2000/svg" >
@@ -41,7 +41,7 @@ exports[` renders buttons 1`] = ` xmlns="http://www.w3.org/2000/svg" >
diff --git a/test/unit-tests/components/views/messages/TextualBody-test.tsx b/test/unit-tests/components/views/messages/TextualBody-test.tsx index a10e4bebe0..753534a93f 100644 --- a/test/unit-tests/components/views/messages/TextualBody-test.tsx +++ b/test/unit-tests/components/views/messages/TextualBody-test.tsx @@ -186,7 +186,7 @@ describe("", () => { const { container } = getComponent({ mxEvent: ev }); const content = container.querySelector(".mx_EventTile_body"); expect(content.innerHTML).toMatchInlineSnapshot( - `"Chat with Member"`, + `"Chat with Member"`, ); }); @@ -204,7 +204,7 @@ describe("", () => { const { container } = getComponent({ mxEvent: ev }); const content = container.querySelector(".mx_EventTile_body"); expect(content.innerHTML).toMatchInlineSnapshot( - `"Visit #room:example.com"`, + `"Visit #room:example.com"`, ); }); 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 a3b9fb205f..599a7719d5 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,7 +35,7 @@ exports[`DecryptionFailureBody should handle messages from users who change iden xmlns="http://www.w3.org/2000/svg" > Sender's verified identity has changed 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 de31628ec3..14007bd61a 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[` when map display is not configured renders maps unavail xmlns="http://www.w3.org/2000/svg" >

should show a download button in file rendering type 1`] = class="mx_MFileBody_download" > should show a download button in file rendering type 1`] = xmlns="http://www.w3.org/2000/svg" > Download 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 7b919b5326..48dc54241f 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 @@ -58,7 +58,7 @@ exports[`MLocationBody without error renders map correctly 1`] = xmlns="http://www.w3.org/2000/svg" > @@ -92,7 +92,7 @@ exports[`MLocationBody without error renders marker correctly fo class="mx_Marker_border" > 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 1ea245acf0..44672397ac 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 @@ -86,7 +86,7 @@ exports[` renders formatted m.text correctly pills appear for an Message from Member"`; +exports[` 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 Message from Member"`; -exports[` 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 Message in Room 2"`; +exports[` 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 Message in Room 2"`; exports[` renders plain-text m.text correctly should pillify a permalink to an unknown message in the same room with the label »Message« 1`] = `
renders plain-text m.text correctly should pillify a pe xmlns="http://www.w3.org/2000/svg" > Poll detail navigates back to poll list from detail vie xmlns="http://www.w3.org/2000/svg" > Active polls 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 21ec64e9b3..dc85fba985 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,7 +12,7 @@ exports[` should close when clicking X button 1`] = ` class="mx_BaseCard_header_title" >

Heading text @@ -20,14 +20,14 @@ exports[` should close when clicking X button 1`] = `

message case AskToJoin renders the corresponding mes

message case AskToJoin renders the corresponding mes

with an invite with an invited email when client has

with an invite without an invited email for a dm roo

with an invite without an invited email for a non-dm

should render invite 1`] = ` class="mx_BaseCard_header_title" >

Profile @@ -20,14 +20,14 @@ exports[` should render invite 1`] = ` @@ -39,7 +39,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -58,7 +58,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -77,7 +77,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -96,7 +96,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -115,7 +115,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -134,7 +134,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -153,7 +153,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -172,7 +172,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -191,7 +191,7 @@ exports[`FormattingButtons renders in german 1`] = ` xmlns="http://www.w3.org/2000/svg" > diff --git a/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx b/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx index 5685f1eb8f..6d3d3de692 100644 --- a/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx +++ b/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx @@ -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/SetIdServer-test.tsx b/test/unit-tests/components/views/settings/SetIdServer-test.tsx new file mode 100644 index 0000000000..d92b7c2737 --- /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("", () => { + 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 = () => ( + + + + ); + + 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 } = 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(getByRole("button", { name: "Continue" })); + }); + + it("should clear input on cancel", async () => { + const { getByLabelText, getByRole } = render(getComponent()); + const identServerField = getByLabelText("Enter a new identity server"); + await userEvent.type(identServerField, "https://identity.example.org"); + await userEvent.click(getByRole("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/__snapshots__/LayoutSwitcher-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap index 2aa08adb94..e8ab3ccd2f 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,25 +19,25 @@ exports[` should render 1`] = ` class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" >

  1. flow to change the recovery key should display th
  2. Change recovery key @@ -61,7 +61,7 @@ exports[` flow to change the recovery key should display th class="mx_EncryptionCard_header" >
    flow to change the recovery key should display th xmlns="http://www.w3.org/2000/svg" >

    Change recovery key?

    @@ -89,32 +89,32 @@ exports[` flow to change the recovery key should display th class="mx_KeyPanel" > Recovery key
    encoded private key Do not share this with anyone!
    flow to set up a recovery key should ask the user />
    After clicking continue, we’ll generate a recovery key for you.
    1. should display the 'forgot recovery key' variant
    2. Reset encryption @@ -55,13 +55,13 @@ exports[` should display the 'forgot recovery key' variant
    should display the 'forgot recovery key' variant xmlns="http://www.w3.org/2000/svg" >

    Forgot your recovery key? You’ll need to reset your identity.

    • should display the 'forgot recovery key' variant xmlns="http://www.w3.org/2000/svg" > Your account details, contacts, preferences, and chat list will be kept
    • should display the 'forgot recovery key' variant xmlns="http://www.w3.org/2000/svg" > You will lose any message history that’s stored only on the server
    • should display the 'forgot recovery key' variant xmlns="http://www.w3.org/2000/svg" > @@ -155,10 +156,11 @@ exports[` should display the 'forgot recovery key' variant
    @@ -1455,7 +1455,7 @@ exports[` matches the snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" >
    @@ -1479,7 +1479,7 @@ exports[` matches the snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1503,7 +1503,7 @@ exports[` matches the snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1527,7 +1527,7 @@ exports[` matches the snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1551,7 +1551,7 @@ exports[` matches the snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1575,7 +1575,7 @@ exports[` matches the snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1599,7 +1599,7 @@ exports[` matches the snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > 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 7478ce1b16..f46355bbd0 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`] > @@ -98,7 +98,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests fully 1`] xmlns="http://www.w3.org/2000/svg" > @@ -123,7 +123,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests reduced 1` class="mx_PeopleRoomSettingsTab_knock" > @@ -179,7 +179,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests reduced 1` xmlns="http://www.w3.org/2000/svg" > 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 625aafe731..171fa4b827 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 @@ -263,6 +263,7 @@ describe("", () => { }); afterAll(() => { + // @ts-expect-error window.location = realWindowLocation; }); 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 0f0e193088..82e33ef47b 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,19 +32,19 @@ exports[`AppearanceUserSettingsTab should render 1`] = ` class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" >
  3. @user48:example.com
    Message #48
  4. @user47:example.com
    Message #47
  5. @user46:example.com
    Message #46
  6. @user45:example.com
    Message #45
  7. @user44:example.com
    Message #44
  8. @user43:example.com
    Message #43
  9. @user42:example.com
    Message #42
  10. @user41:example.com
    Message #41
  11. @user40:example.com
    Message #40
  12. @user39:example.com
    Message #39
  13. @user38:example.com
    Message #38
  14. @user37:example.com
    Message #37
  15. @user36:example.com
    Message #36
  16. @user35:example.com
    Message #35
  17. @user34:example.com
    Message #34
  18. @user33:example.com
    Message #33
  19. @user32:example.com
    Message #32
  20. @user31:example.com
    Message #31
  21. @user30:example.com
    Message #30
  22. @user29:example.com
    Message #29
  23. @user28:example.com
    Message #28
  24. @user27:example.com
    Message #27
  25. @user26:example.com
    Message #26
  26. @user25:example.com
    Message #25
  27. @user24:example.com
    Message #24
  28. @user23:example.com
    Message #23
  29. @user22:example.com
    Message #22
  30. @user21:example.com
    Message #21
  31. @user20:example.com
    Message #20
  32. @user19:example.com
    Message #19
  33. @user18:example.com
    Message #18
  34. @user17:example.com
    Message #17
  35. @user16:example.com
    Message #16
  36. @user15:example.com
    Message #15
  37. @user14:example.com
    Message #14
  38. @user13:example.com
    Message #13
  39. @user12:example.com
    Message #12
  40. @user11:example.com
    Message #11
  41. @user10:example.com
    Message #10
  42. @user9:example.com
    Message #9
  43. @user8:example.com
    Message #8
  44. @user7:example.com
    Message #7
  45. @user6:example.com
    Message #6
  46. @user5:example.com
    Message #5
  47. @user4:example.com
    Message #4
  48. @user3:example.com
    Message #3
  49. @user2:example.com
    Message #2
  50. @user1:example.com
    Message #1
  51. @user0:example.com
    Message #0
  52. +
  53. @user49:example.com
    Message #49
  54. @user48:example.com
    Message #48
  55. @user47:example.com
    Message #47
  56. @user46:example.com
    Message #46
  57. @user45:example.com
    Message #45
  58. @user44:example.com
    Message #44
  59. @user43:example.com
    Message #43
  60. @user42:example.com
    Message #42
  61. @user41:example.com
    Message #41
  62. @user40:example.com
    Message #40
  63. @user39:example.com
    Message #39
  64. @user38:example.com
    Message #38
  65. @user37:example.com
    Message #37
  66. @user36:example.com
    Message #36
  67. @user35:example.com
    Message #35
  68. @user34:example.com
    Message #34
  69. @user33:example.com
    Message #33
  70. @user32:example.com
    Message #32
  71. @user31:example.com
    Message #31
  72. @user30:example.com
    Message #30
  73. @user29:example.com
    Message #29
  74. @user28:example.com
    Message #28
  75. @user27:example.com
    Message #27
  76. @user26:example.com
    Message #26
  77. @user25:example.com
    Message #25
  78. @user24:example.com
    Message #24
  79. @user23:example.com
    Message #23
  80. @user22:example.com
    Message #22
  81. @user21:example.com
    Message #21
  82. @user20:example.com
    Message #20
  83. @user19:example.com
    Message #19
  84. @user18:example.com
    Message #18
  85. @user17:example.com
    Message #17
  86. @user16:example.com
    Message #16
  87. @user15:example.com
    Message #15
  88. @user14:example.com
    Message #14
  89. @user13:example.com
    Message #13
  90. @user12:example.com
    Message #12
  91. @user11:example.com
    Message #11
  92. @user10:example.com
    Message #10
  93. @user9:example.com
    Message #9
  94. @user8:example.com
    Message #8
  95. @user7:example.com
    Message #7
  96. @user6:example.com
    Message #6
  97. @user5:example.com
    Message #5
  98. @user4:example.com
    Message #4
  99. @user3:example.com
    Message #3
  100. @user2:example.com
    Message #2
  101. @user1:example.com
    Message #1
  102. @user0:example.com
    Message #0
diff --git a/test/unit-tests/utils/oidc/authorize-test.ts b/test/unit-tests/utils/oidc/authorize-test.ts index cade0c42ca..3ccc77e633 100644 --- a/test/unit-tests/utils/oidc/authorize-test.ts +++ b/test/unit-tests/utils/oidc/authorize-test.ts @@ -65,6 +65,7 @@ describe("OIDC authorization", () => { }); afterAll(() => { + // @ts-expect-error window.location = realWindowLocation; }); diff --git a/test/unit-tests/vector/__snapshots__/init-test.ts.snap b/test/unit-tests/vector/__snapshots__/init-test.ts.snap index 4fd8e03459..6bc1ebd0d1 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" >

Error title

msg1

msg2

@@ -53,17 +53,17 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = ` class="mx_ErrorView_container" >

Element does not support this browser

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.

For the best experience, use @@ -106,7 +106,7 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = ` style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);" >