diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5a762fe224..4d173ab222 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,12 +4,13 @@ /pnpm-lock.yaml @element-hq/element-web-team /apps/web/src/SecurityManager.ts @element-hq/element-crypto-web-reviewers -/apps/web/test/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers +/apps/web/test/unit-tests/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers /apps/web/src/async-components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers +/apps/web/test/unit-tests/async-components/dialogs/security/ @element-hq/element-crypto-web-reviewers /apps/web/src/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers -/apps/web/test/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers +/apps/web/test/unit-tests/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers /apps/web/src/stores/SetupEncryptionStore.ts @element-hq/element-crypto-web-reviewers -/apps/web/test/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers +/apps/web/test/unit-tests/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers /apps/web/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx @element-hq/element-crypto-web-reviewers /apps/web/src/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers /apps/web/test/unit-tests/components/views/settings/encryption/ @element-hq/element-crypto-web-reviewers diff --git a/.github/actions/download-verify-element-tarball/action.yml b/.github/actions/download-verify-element-tarball/action.yml index a64bc3241b..40855b85c6 100644 --- a/.github/actions/download-verify-element-tarball/action.yml +++ b/.github/actions/download-verify-element-tarball/action.yml @@ -31,7 +31,9 @@ runs: - name: Move webapp to out-file-path shell: bash - run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }} + run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp "$OUT_PATH" + env: + OUT_PATH: ${{ inputs.out-file-path }} - name: Clean up temp directory shell: bash diff --git a/.github/actions/setup-playwright/action.yml b/.github/actions/setup-playwright/action.yml new file mode 100644 index 0000000000..1276191d2b --- /dev/null +++ b/.github/actions/setup-playwright/action.yml @@ -0,0 +1,49 @@ +name: Setup playwright +description: Installs playwright browsers and sets up a cache +inputs: + needs-webkit: + description: Whether to install the additional dependencies for webkit + required: false + default: "false" + write-cache: + description: Whether to write the cache back + required: true +runs: + using: composite + steps: + - name: Calculate cache key + id: key + run: | + PW_VERSION=$(pnpm --silent -- playwright --version | awk '{print $2}') + echo "key=${PREFIX}-playwright-${PW_VERSION}" >> $GITHUB_OUTPUT + shell: bash + env: + PREFIX: ${{ runner.os }}-${{ runner.arch }} + + - name: Cache playwright binaries + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + if: inputs.write-cache == 'true' + id: cache + with: + path: ~/.cache/ms-playwright + key: ${{ steps.key.outputs.key }} + + # When running in merge queue only restore the cache, never write it + - name: Restore playwright binaries cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + if: inputs.write-cache != 'true' + id: cache-restore + with: + path: ~/.cache/ms-playwright + key: ${{ steps.key.outputs.key }} + + - name: Install Playwright browsers + if: (steps.cache.outputs.cache-hit || steps.cache-restore.outputs.cache-hit) != 'true' + shell: bash + run: pnpm playwright install --with-deps + + # Some WebKit dependencies seem to lay outside the cache and will need to be installed separately + - name: Install system dependencies for WebKit + if: inputs.needs-webkit == 'true' && (steps.cache.outputs.cache-hit || steps.cache-restore.outputs.cache-hit) == 'true' + shell: bash + run: pnpm playwright install-deps webkit diff --git a/.github/renovate.json b/.github/renovate.json index 5afa859d07..9bc8cd62b3 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -2,6 +2,14 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["github>matrix-org/renovate-config-element-web"], "postUpdateOptions": ["pnpmDedupe"], + "packageRules": [ + { + "groupName": "testcontainers docker digests", + "groupSlug": "testcontainers-docker", + "matchDepTypes": ["testcontainers-docker"], + "matchPackageNames": ["*"] + } + ], "customManagers": [ { "customType": "regex", @@ -9,7 +17,16 @@ "versioningTemplate": "loose", "description": "Update testcontainers docker digests", "managerFilePatterns": ["**/testcontainers/*.ts"], - "matchStrings": ["\\s+\"(?[^@]+):(?[^@]+)@(?sha256:[a-f0-9]+)\""] + "matchStrings": ["\\s+\"(?[^@]+):(?[^@]+)@(?sha256:[a-f0-9]+)\""], + "depTypeTemplate": "testcontainers-docker" + }, + { + "customType": "jsonata", + "managerFilePatterns": ["/(^|/)package\\.json$/"], + "fileFormat": "json", + "matchStrings": ["hakDependencies.$each(function($v, $k) { { 'packageName': $k, 'currentValue': $v } })"], + "datasourceTemplate": "npm", + "depTypeTemplate": "hak" } ] } diff --git a/.github/workflows/end-to-end-tests-netlify.yaml b/.github/workflows/build-and-test-netlify.yaml similarity index 97% rename from .github/workflows/end-to-end-tests-netlify.yaml rename to .github/workflows/build-and-test-netlify.yaml index c5bca0e192..1b3aa2bd69 100644 --- a/.github/workflows/end-to-end-tests-netlify.yaml +++ b/.github/workflows/build-and-test-netlify.yaml @@ -5,7 +5,7 @@ on: # Privilege escalation necessary to publish to Netlify # 🚨 We must not execute any checked out code here. workflow_run: # zizmor: ignore[dangerous-triggers] - workflows: ["End to End Tests"] + workflows: ["Build & Test"] types: - completed diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/build-and-test.yaml similarity index 61% rename from .github/workflows/end-to-end-tests.yaml rename to .github/workflows/build-and-test.yaml index 3d72750a1d..eb469502d9 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/build-and-test.yaml @@ -1,7 +1,13 @@ -# Produce a build of element-web with this version of react-sdk -# and any matching branches of element-web and js-sdk, output it -# as an artifact and run end-to-end tests. -name: End to End Tests +# builds Element Web +# runs Playwright tests against the built Element Web +# builds Element Desktop using the built Element Web +# +# Tries to use a matching js-sdk branch for the build. +# +# Produces a `webapp` artifact +# Produces multiple Desktop artifacts +# Produces multiple Playwright report artifacts +name: Build & Test on: # CRON to run all Projects at 6am UTC schedule: @@ -10,9 +16,8 @@ on: merge_group: types: [checks_requested] push: - branches: [develop, master] - repository_dispatch: - types: [element-web-notify] + # We do not build on push to develop as the merge_group check handles that + branches: [staging, master] # support triggering from other workflows workflow_call: @@ -35,20 +40,22 @@ concurrency: env: # fetchdep.sh needs to know our PR number PR_NUMBER: ${{ github.event.pull_request.number }} - # Use 6 runners in the default case, but 4 when running on a schedule where we run all 5 projects (20 runners total) - NUM_RUNNERS: ${{ github.event_name == 'schedule' && 4 || 6 }} + # Use 4 runners in the default case, but only 1 when running on a schedule where we run all 5 projects + NUM_RUNNERS: ${{ github.event_name == 'schedule' && 1 || 4 }} NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes permissions: {} # No permissions required jobs: - build: - name: "Build Element-Web" + build_ew: + name: "Build Element Web" runs-on: ubuntu-24.04 if: inputs.skip != true outputs: num-runners: ${{ env.NUM_RUNNERS }} runners-matrix: ${{ steps.runner-vars.outputs.matrix }} + # Skip pull_request runs on renovate PRs to speed up CI time, delegating to the full run in merge queue + skip: ${{ inputs.skip || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -79,7 +86,7 @@ jobs: run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build - name: Upload Artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: webapp path: apps/web/webapp @@ -87,17 +94,17 @@ jobs: - name: Calculate runner variables id: runner-vars - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | const numRunners = parseInt(process.env.NUM_RUNNERS, 10); const matrix = Array.from({ length: numRunners }, (_, i) => i + 1); core.setOutput("matrix", JSON.stringify(matrix)); - playwright: - name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}" - needs: build - if: inputs.skip != true + playwright_ew: + name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build_ew.outputs.num-runners }}" + needs: build_ew + if: needs.build_ew.outputs.skip == 'false' runs-on: ubuntu-24.04 permissions: actions: read @@ -107,7 +114,7 @@ jobs: fail-fast: false matrix: # Run multiple instances in parallel to speed up the tests - runner: ${{ fromJSON(needs.build.outputs.runners-matrix) }} + runner: ${{ fromJSON(needs.build_ew.outputs.runners-matrix) }} project: - Chrome - Firefox @@ -148,105 +155,147 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Get installed Playwright version - id: playwright - run: echo "version=$(pnpm --silent -- playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT - - - name: Cache playwright binaries - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 - id: playwright-cache + - name: Setup playwright + uses: ./.github/actions/setup-playwright with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }} - - - name: Install Playwright browsers - if: steps.playwright-cache.outputs.cache-hit != 'true' - working-directory: apps/web - run: pnpm playwright install --with-deps --no-shell - - - name: Install system dependencies for WebKit - # Some WebKit dependencies seem to lay outside the cache and will need to be installed separately - if: matrix.project == 'WebKit' && steps.playwright-cache.outputs.cache-hit == 'true' - working-directory: apps/web - run: pnpm playwright install-deps webkit + needs-webkit: ${{ matrix.project == 'WebKit' }} + write-cache: ${{ github.event_name != 'merge_group' }} # We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else - name: Run Playwright tests working-directory: apps/web run: | - pnpm playwright test \ + pnpm test:playwright \ --shard "$SHARD" \ --project="${{ matrix.project }}" \ ${{ (github.event_name == 'pull_request' && matrix.runAllTests == false ) && '--grep-invert @mergequeue' || '' }} env: - SHARD: ${{ format('{0}/{1}', matrix.runner, needs.build.outputs.num-runners) }} + SHARD: ${{ format('{0}/{1}', matrix.runner, needs.build_ew.outputs.num-runners) }} - name: Upload blob report to GitHub Actions Artifacts if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }} + name: blob-report-${{ matrix.project }}-${{ matrix.runner }} path: apps/web/blob-report retention-days: 1 + if-no-files-found: error downstream-modules: name: Downstream Playwright tests [element-modules] - needs: build - if: inputs.skip != true && github.event_name == 'merge_group' + needs: build_ew + if: needs.build_ew.outputs.skip == 'false' && github.event_name == 'merge_group' uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main # zizmor: ignore[unpinned-uses] with: webapp-artifact: webapp + reporter: blob + + prepare_ed: + name: "Prepare Element Desktop" + uses: ./.github/workflows/build_desktop_prepare.yaml + needs: build_ew + if: needs.build_ew.outputs.skip == 'false' + permissions: + contents: read + with: + config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }} + version: ${{ case((github.event.pull_request.base.ref || github.ref_name) == 'develop' || github.event_name == 'merge_group', 'develop', '') }} + webapp-artifact: webapp + + build_ed_windows: + needs: prepare_ed + name: "Desktop Windows" + uses: ./.github/workflows/build_desktop_windows.yaml + strategy: + matrix: + arch: [x64, ia32, arm64] + with: + arch: ${{ matrix.arch }} + blob_report: true + + build_ed_linux: + needs: prepare_ed + name: "Desktop Linux" + uses: ./.github/workflows/build_desktop_linux.yaml + strategy: + matrix: + sqlcipher: [system, static] + arch: [amd64, arm64] + runAllTests: + - ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests') }} + # We ship static sqlcipher builds, so delegate testing the system builds to the merge queue + exclude: + - runAllTests: false + sqlcipher: system + with: + sqlcipher: ${{ matrix.sqlcipher }} + arch: ${{ matrix.arch }} + blob_report: true + + build_ed_macos: + needs: prepare_ed + name: "Desktop macOS" + uses: ./.github/workflows/build_desktop_macos.yaml + with: + blob_report: true complete: name: end-to-end-tests needs: - - playwright + - build_ew + - playwright_ew - downstream-modules + - prepare_ed + - build_ed_windows + - build_ed_linux + - build_ed_macos if: always() runs-on: ubuntu-24.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' with: persist-credentials: false repository: element-hq/element-web - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' with: cache: "pnpm" node-version: "lts/*" - name: Install dependencies - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' run: pnpm install --frozen-lockfile - name: Download blob reports from GitHub Actions Artifacts - if: inputs.skip != true + if: needs.build_ew.outputs.skip == 'false' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - pattern: all-blob-reports-* - path: apps/web/all-blob-reports + pattern: blob-report-* + path: all-blob-reports merge-multiple: true - name: Merge into HTML Report - if: inputs.skip != true - working-directory: apps/web - run: pnpm playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts,@element-hq/element-web-playwright-common/lib/stale-screenshot-reporter.js ./all-blob-reports + if: needs.build_ew.outputs.skip == 'false' + run: | + pnpm playwright merge-reports \ + --config=playwright-merge.config.ts \ + ./all-blob-reports env: # Only pass creds to the flaky-reporter on main branch runs GITHUB_TOKEN: ${{ github.ref_name == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }} - PLAYWRIGHT_HTML_TITLE: ${{ case(github.event_name == 'pull_request', format('EW Playwright Report PR-{0}', env.PR_NUMBER), 'EW Playwright Report') }} + PLAYWRIGHT_HTML_TITLE: ${{ case(github.event_name == 'pull_request', format('Playwright Report PR-{0}', env.PR_NUMBER), 'Playwright Report') }} # Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected - name: Upload HTML report - if: always() && inputs.skip != true - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + if: always() && needs.build_ew.outputs.skip == 'false' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: html-report - path: apps/web/playwright-report + path: playwright-report retention-days: 14 if-no-files-found: error diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a78e4f25f8..c63be7a419 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,7 +69,7 @@ jobs: run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build - name: Upload Artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: webapp-${{ matrix.image }} path: apps/web/webapp diff --git a/.github/workflows/build_debian.yaml b/.github/workflows/build_debian.yaml index c924bb8949..24fe492e7c 100644 --- a/.github/workflows/build_debian.yaml +++ b/.github/workflows/build_debian.yaml @@ -69,7 +69,7 @@ jobs: dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: element-web.deb path: apps/web/element-web.deb diff --git a/.github/workflows/build_desktop_and_deploy.yaml b/.github/workflows/build_desktop_and_deploy.yaml index 0455b7e37c..5611b2d4fd 100644 --- a/.github/workflows/build_desktop_and_deploy.yaml +++ b/.github/workflows/build_desktop_and_deploy.yaml @@ -212,7 +212,7 @@ jobs: - name: Stash packages.element.io if: needs.prepare.outputs.deploy == 'false' - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: packages.element.io path: packages.element.io @@ -250,7 +250,7 @@ jobs: - name: Stash debs if: needs.prepare.outputs.deploy == 'false' && needs.linux.result == 'success' - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: debs path: | @@ -289,7 +289,7 @@ jobs: id-token: write # This is required for requesting the JWT steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 + uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6 with: role-to-assume: arn:aws:iam::264135176173:role/Push-ElementDesktop-MSI role-session-name: githubaction-run-${{ github.run_id }} diff --git a/.github/workflows/build_desktop_and_test.yaml b/.github/workflows/build_desktop_and_test.yaml deleted file mode 100644 index 1ae64a87e4..0000000000 --- a/.github/workflows/build_desktop_and_test.yaml +++ /dev/null @@ -1,89 +0,0 @@ -name: Build and Test -on: - pull_request: {} - push: - branches: [develop, staging, master] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: {} # No permissions required -jobs: - fetch: - uses: ./.github/workflows/build_desktop_prepare.yaml - permissions: - contents: read - with: - config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }} - version: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'develop' || '' }} - branch-matching: true - - windows: - needs: fetch - name: Windows - uses: ./.github/workflows/build_desktop_windows.yaml - strategy: - matrix: - arch: [x64, ia32, arm64] - with: - arch: ${{ matrix.arch }} - blob_report: true - - linux: - needs: fetch - name: "Linux (${{ matrix.arch }}) (sqlcipher: ${{ matrix.sqlcipher }})" - uses: ./.github/workflows/build_desktop_linux.yaml - strategy: - matrix: - sqlcipher: [system, static] - arch: [amd64, arm64] - with: - sqlcipher: ${{ matrix.sqlcipher }} - arch: ${{ matrix.arch }} - blob_report: true - - macos: - needs: fetch - name: macOS - uses: ./.github/workflows/build_desktop_macos.yaml - with: - blob_report: true - - tests-done: - needs: [windows, linux, macos] - runs-on: ubuntu-24.04 - if: ${{ !cancelled() }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - cache: "pnpm" - node-version: "lts/*" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - pattern: blob-report-* - path: apps/desktop/all-blob-reports - merge-multiple: true - - - name: Merge into HTML Report - working-directory: apps/desktop - run: pnpm playwright merge-reports -c ./playwright.config.ts --reporter=html ./all-blob-reports - - - name: Upload HTML report - if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: html-report - path: apps/desktop/playwright-report - retention-days: 14 - - - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - run: exit 1 diff --git a/.github/workflows/build_desktop_linux.yaml b/.github/workflows/build_desktop_linux.yaml index 938ae9e132..767fa06c10 100644 --- a/.github/workflows/build_desktop_linux.yaml +++ b/.github/workflows/build_desktop_linux.yaml @@ -28,7 +28,7 @@ on: type: string required: false description: | - The name of the prepare artifact to use, defaults to 'webapp'. + The name of the prepare artifact to use, defaults to 'desktop-prepare'. The artifact must contain the following: + webapp.asar - the asar archive of the webapp to embed in the desktop app + electronVersion - the version of electron to use for cache keying @@ -38,7 +38,7 @@ on: The artifact can also contain any additional files which will be applied as overrides to the checkout root before building, for example icons in the `build/` directory to override the app icons. - default: "webapp" + default: "desktop-prepare" test: type: boolean required: false @@ -73,20 +73,8 @@ jobs: # https://github.com/matrix-org/seshat/issues/135 runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }} env: - HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-web/desktop-build-env + HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-web/desktop-build-env:${{ case(github.event_name == 'push', inputs.ref || github.ref_name, github.event_name == 'release', 'staging', 'develop') }} steps: - - name: Resolve docker image tag for push - if: github.event_name == 'push' - run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:$REF" >> $GITHUB_ENV - env: - REF: ${{ inputs.ref || github.ref_name }} - - name: Resolve docker image tag for release - if: github.event_name == 'release' - run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:staging" >> $GITHUB_ENV - - name: Resolve docker image tag for other triggers - if: github.event_name != 'push' && github.event_name != 'release' - run: echo "HAK_DOCKER_IMAGE=$HAK_DOCKER_IMAGE:develop" >> $GITHUB_ENV - - uses: nbucic/variable-mapper@0673f6891a0619ba7c002ecfed0f9f4f39017b6f id: config with: @@ -95,11 +83,9 @@ jobs: map: | { "amd64": { - "target": "x86_64-unknown-linux-gnu", "arch": "x86-64" }, "arm64": { - "target": "aarch64-unknown-linux-gnu", "arch": "aarch64", "build-args": "--arm64" } @@ -118,9 +104,9 @@ jobs: - name: Cache .hak id: cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: - key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion', 'dockerbuild/*') }} + key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion', 'apps/desktop/dockerbuild/*') }} path: | apps/desktop/.hak @@ -135,7 +121,7 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: pnpm install --frozen-lockfile + run: "pnpm install --frozen-lockfile --filter element-desktop" - name: "Get modified files" id: changed_files @@ -147,7 +133,7 @@ jobs: # This allows contributors to test changes to the dockerbuild image within a pull request - name: Build docker image - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 if: steps.changed_files.outputs.any_modified == 'true' with: file: apps/desktop/dockerbuild/Dockerfile @@ -230,7 +216,7 @@ jobs: # We exclude *-unpacked as it loses permissions and the tarball contains it with correct permissions - name: Upload Artifacts - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }} path: | diff --git a/.github/workflows/build_desktop_macos.yaml b/.github/workflows/build_desktop_macos.yaml index f9b901e951..384506518b 100644 --- a/.github/workflows/build_desktop_macos.yaml +++ b/.github/workflows/build_desktop_macos.yaml @@ -37,7 +37,7 @@ on: type: string required: false description: | - The name of the prepare artifact to use, defaults to 'webapp'. + The name of the prepare artifact to use, defaults to 'desktop-prepare'. The artifact must contain the following: + webapp.asar - the asar archive of the webapp to embed in the desktop app + electronVersion - the version of electron to use for cache keying @@ -46,7 +46,7 @@ on: The artifact can also contain any additional files which will be applied as overrides to the checkout root before building, for example icons in the `build/` directory to override the app icons. - default: "webapp" + default: "desktop-prepare" test: type: boolean required: false @@ -90,9 +90,9 @@ jobs: - name: Cache .hak id: cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: - key: ${{ runner.os }}-${{ hashFiles('hakHash', 'electronVersion') }} + key: ${{ runner.os }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }} path: | apps/desktop/.hak @@ -121,28 +121,29 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - name: Build Natives if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop run: pnpm run build:native:universal - - name: "Build App" + # We split these because electron-builder gets upset if we set CSC_LINK even to an empty string + - name: "[Signed] Build App" + if: inputs.sign != '' working-directory: apps/desktop - run: pnpm run build:universal --publish never -m ${TARGETS} + run: | + pnpm run build:universal --publish never -m ${TARGETS} env: - # Code signing parameters - CSC_IDENTITY_AUTO_DISCOVERY: ${{ inputs.sign != '' }} - APPLE_TEAM_ID: ${{ case(inputs.sign != '', vars.APPLE_TEAM_ID, '') }} - APPLE_ID: ${{ case(inputs.sign != '', secrets.APPLE_ID, '') }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ case(inputs.sign != '', secrets.APPLE_ID_PASSWORD, '') }} - CSC_KEY_PASSWORD: ${{ case(inputs.sign != '', secrets.APPLE_CSC_KEY_PASSWORD, '') }} - CSC_LINK: ${{ case(inputs.sign != '', secrets.APPLE_CSC_LINK, '') }} + APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }} + CSC_LINK: ${{ secrets.APPLE_CSC_LINK }} VARIANT_PATH: variant.json - TARGETS: ${{ inputs.targets }} # Only set for Nightly builds VERSION: ${{ inputs.version }} + TARGETS: ${{ inputs.targets }} - name: Check app was signed & notarised successfully if: inputs.sign != '' @@ -153,6 +154,16 @@ jobs: spctl -a -vvv -t install /Volumes/Element/*.app hdiutil detach /Volumes/Element + - name: "[Unsigned] Build App" + if: inputs.sign == '' + working-directory: apps/desktop + run: | + pnpm run build:universal --publish never -m ${TARGETS} + env: + CSC_IDENTITY_AUTO_DISCOVERY: false + VARIANT_PATH: variant.json + TARGETS: ${{ inputs.targets }} + - name: Generate releases.json if: inputs.base-url working-directory: apps/desktop @@ -183,7 +194,7 @@ jobs: # We exclude mac-universal as the unpacked app takes forever to upload and zip and dmg already contains it - name: Upload Artifacts - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ inputs.artifact-prefix }}macos path: | diff --git a/.github/workflows/build_desktop_prepare.yaml b/.github/workflows/build_desktop_prepare.yaml index 517b865ab1..be782f9dc1 100644 --- a/.github/workflows/build_desktop_prepare.yaml +++ b/.github/workflows/build_desktop_prepare.yaml @@ -20,11 +20,10 @@ on: required: false default: false description: "Whether the build should be deployed to production" - branch-matching: - type: boolean + webapp-artifact: + type: string required: false - default: false - description: "Whether the branch name should be matched to find the element-web commit" + description: "Name of the webapp artifact that should be used, will fetch a relevant build if omitted" secrets: # Required if `nightly` is set CF_R2_ACCESS_KEY_ID: @@ -57,6 +56,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false + repository: element-hq/element-web - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 @@ -66,28 +66,26 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - - name: Fetch Element Web (matching branch) - id: branch-matching - if: inputs.branch-matching + - name: Fetch Element Web (from artifact) + if: inputs.webapp-artifact != '' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: ${{ inputs.webapp-artifact }} + path: apps/desktop/webapp + + - name: Build webapp.asar (from artifact) + if: inputs.webapp-artifact != '' working-directory: apps/desktop - continue-on-error: true run: | - scripts/branch-match.sh - cp "$CONFIG_DIR/config.json" element-web/ - pnpm --cwd element-web install --frozen-lockfile - pnpm --cwd element-web run build - mv element-web/webapp . + cp -f "$CONFIG_DIR/config.json" webapp/config.json pnpm run asar-webapp env: - # These must be set for branch-match.sh to get the right branch - REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} CONFIG_DIR: ${{ inputs.config }} - name: Fetch Element Web (${{ inputs.version }}) - if: steps.branch-matching.outcome == 'failure' || steps.branch-matching.outcome == 'skipped' + if: inputs.webapp-artifact == '' working-directory: apps/desktop run: pnpm run fetch --noverify -d ${CONFIG} ${VERSION} env: @@ -187,9 +185,9 @@ jobs: env: NIGHTLY_VERSION: ${{ steps.versions.outputs.nightly }} - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: webapp + name: desktop-prepare retention-days: 1 path: | apps/desktop/webapp.asar diff --git a/.github/workflows/build_desktop_test.yaml b/.github/workflows/build_desktop_test.yaml index 09fd9e46e9..613a72cfb0 100644 --- a/.github/workflows/build_desktop_test.yaml +++ b/.github/workflows/build_desktop_test.yaml @@ -38,7 +38,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - repository: ${{ github.repository == 'element-hq/element-web-pro' && 'element-hq/element-web' || github.repository }} + repository: element-hq/element-web persist-credentials: false - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5 @@ -48,8 +48,7 @@ jobs: cache: "pnpm" - name: Install Deps - working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: @@ -85,25 +84,34 @@ jobs: EXECUTABLE: ${{ steps.executable.outputs.path }} - name: Run tests - uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a timeout-minutes: 20 - with: - run: pnpm -C apps/desktop test --project=${{ inputs.project }} ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} ${{ inputs.blob_report == false && '--reporter=html' || '' }} ${{ inputs.args }} + shell: bash + working-directory: apps/desktop + run: | + $PREFIX pnpm playwright test \ + ${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} \ + ${{ inputs.blob_report == false && '--reporter=html' || '' }} \ + $ARGS env: + PREFIX: ${{ runner.os == 'Linux' && 'xvfb-run' || '' }} + PW_TAG: ${{ inputs.project }} ELEMENT_DESKTOP_EXECUTABLE: ${{ steps.executable.outputs.path }} + ARGS: ${{ inputs.args }} - name: Upload blob report if: always() && inputs.blob_report - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: blob-report-${{ inputs.artifact }} path: apps/desktop/blob-report retention-days: 1 + if-no-files-found: error - name: Upload HTML report if: always() && inputs.blob_report == false - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ inputs.artifact }}-test path: apps/desktop/playwright-report retention-days: 14 + if-no-files-found: error diff --git a/.github/workflows/build_desktop_windows.yaml b/.github/workflows/build_desktop_windows.yaml index 37fc8679aa..e893e5188c 100644 --- a/.github/workflows/build_desktop_windows.yaml +++ b/.github/workflows/build_desktop_windows.yaml @@ -42,7 +42,7 @@ on: type: string required: false description: | - The name of the prepare artifact to use, defaults to 'webapp'. + The name of the prepare artifact to use, defaults to 'desktop-prepare'. The artifact must contain the following: + webapp.asar - the asar archive of the webapp to embed in the desktop app + electronVersion - the version of electron to use for cache keying @@ -52,7 +52,7 @@ on: The artifact can also contain any additional files which will be applied as overrides to the checkout root before building, for example icons in the `build/` directory to override the app icons. - default: "webapp" + default: "desktop-prepare" test: type: boolean required: false @@ -121,9 +121,9 @@ jobs: - name: Cache .hak id: cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: - key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('hakHash', 'electronVersion') }} + key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }} path: | apps/desktop/.hak @@ -160,7 +160,7 @@ jobs: - name: Install Deps working-directory: apps/desktop - run: "pnpm install --frozen-lockfile" + run: "pnpm install --frozen-lockfile --filter element-desktop" - name: Insert config snippet if: steps.config.outputs.extra_config != '' @@ -274,7 +274,7 @@ jobs: | ForEach-Object -Process {. $env:SIGNTOOL_PATH verify /pa $_.FullName; if(!$?) { throw }} - name: Upload Artifacts - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }} path: | diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml index 6f85402bb6..1d0c78c7ab 100644 --- a/.github/workflows/build_develop.yml +++ b/.github/workflows/build_develop.yml @@ -60,7 +60,7 @@ jobs: - run: mv dist/element-*.tar.gz dist/develop.tar.gz working-directory: apps/web - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: webapp path: apps/web/dist/develop.tar.gz @@ -111,7 +111,7 @@ jobs: running-workflow-name: "Build & Deploy develop.element.io" repo-token: ${{ secrets.GITHUB_TOKEN }} wait-interval: 10 - check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload).)*$ + check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify).)*$ # We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier # as the expires after 24h and requires auth to download. diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index f96477cc19..137ab69e17 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -2,6 +2,13 @@ name: CD # Continuous Delivery on: push: branches: [master, staging, develop] + paths: + - "**/Dockerfile" + - "**/dockerbuild" + - "**/docker" + - "**/docker-*" + - "pnpm-lock.yaml" + concurrency: ${{ github.workflow }}-${{ github.ref_name }} permissions: {} @@ -43,7 +50,7 @@ jobs: run: "pnpm install --frozen-lockfile" - name: Login to GitHub Container Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index e276754640..c33629c2cd 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -39,7 +39,7 @@ jobs: - name: Build and load id: test-build - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: . file: apps/web/Dockerfile @@ -97,14 +97,14 @@ jobs: latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }} - name: Login to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 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@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 if: github.event_name != 'pull_request' with: registry: ghcr.io @@ -140,7 +140,7 @@ jobs: services/web-repositories/secret/data/oci.element.io password | OCI_PASSWORD ; - name: Login to oci.element.io Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 if: github.event_name != 'pull_request' with: registry: oci-push.vpn.infra.element.io @@ -149,7 +149,7 @@ jobs: - name: Build and push id: build-and-push - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 if: github.event_name != 'pull_request' with: context: . diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bf24169461..0092b61f2c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: run: pnpm run docs:build - name: Upload artifact - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: ./docs/.vitepress/dist diff --git a/.github/workflows/issue_closed.yml b/.github/workflows/issue_closed.yml index 375c2e7184..e42d54dc65 100644 --- a/.github/workflows/issue_closed.yml +++ b/.github/workflows/issue_closed.yml @@ -10,7 +10,7 @@ jobs: name: Tidy closed issues runs-on: ubuntu-24.04 steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 id: main with: # PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org) @@ -142,7 +142,7 @@ jobs: }); } } - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 name: Close duplicate as Not Planned if: steps.main.outputs.closeAsNotPlanned with: diff --git a/.github/workflows/merge-queue.yaml b/.github/workflows/merge-queue.yaml new file mode 100644 index 0000000000..1e4d7d3ede --- /dev/null +++ b/.github/workflows/merge-queue.yaml @@ -0,0 +1,29 @@ +# Tweaks the behaviour of Merge Queue to skip certain checks +name: Merge Queue tweaks +on: + merge_group: + types: [checks_requested] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +permissions: {} + +jobs: + run: + runs-on: ubuntu-24.04 + permissions: + statuses: write + steps: + # This is only needed as license/cla at time of writing seems to be extraordinarily flaky + # and Github doesn't support conditional checks between PR & merge queue. + # This is fine to do as a PR won't make it to merge queue until it has license/cla passing. + - name: Skip license/cla on merge queues + uses: guibranco/github-status-action-v2@9bfa8773cdbdc6c185747fd43cd7faa9d7c32f09 + with: + authToken: ${{ secrets.GITHUB_TOKEN }} + state: success + context: license/cla + sha: ${{ github.sha }} + target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/shared-component-publish.yaml b/.github/workflows/npm-publish.yaml similarity index 68% rename from .github/workflows/shared-component-publish.yaml rename to .github/workflows/npm-publish.yaml index c728c303d5..8ea61e4efa 100644 --- a/.github/workflows/shared-component-publish.yaml +++ b/.github/workflows/npm-publish.yaml @@ -1,6 +1,16 @@ -name: Publish shared component npm package +name: Publish npm package +run-name: Publish ${{ inputs.package }} on: - workflow_dispatch: {} + workflow_dispatch: + inputs: + package: + description: Which package to release + required: true + type: choice + options: + - playwright-common + - shared-components + - module-api concurrency: release jobs: @@ -29,10 +39,9 @@ jobs: - name: Update npm run: npm install -g npm@latest - # Need to setup element web too as it needs the translations - - name: 🛠️ Setup EW + - name: 🛠️ Install dependencies run: pnpm install --frozen-lockfile - name: 🚀 Publish to npm - working-directory: packages/shared-components + working-directory: packages/${{ inputs.package }} run: npm publish --access public --provenance diff --git a/.github/workflows/pull_request_base_branch.yaml b/.github/workflows/pull_request_base_branch.yaml index e79c37783b..32c79071ef 100644 --- a/.github/workflows/pull_request_base_branch.yaml +++ b/.github/workflows/pull_request_base_branch.yaml @@ -8,7 +8,7 @@ jobs: name: Check PR base branch runs-on: ubuntu-24.04 steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | const baseBranch = context.payload.pull_request.base.ref; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a0f5166e3..b1a81af773 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,8 @@ jobs: asset-path: dist/*.tar.gz expected-asset-count: 3 # Desktop has no dist script so we only target web here - dir: apps/web + dist-dir: apps/web + version-dirs: apps/web apps/desktop check: name: Post release checks diff --git a/.github/workflows/shared-component-storybook-build.yml b/.github/workflows/shared-component-storybook-build.yml index 0a19e215dd..53af5a5f7b 100644 --- a/.github/workflows/shared-component-storybook-build.yml +++ b/.github/workflows/shared-component-storybook-build.yml @@ -31,7 +31,7 @@ jobs: working-directory: packages/shared-components run: pnpm build:storybook - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: shared-components-storybook path: packages/shared-components/storybook-static diff --git a/.github/workflows/shared-component-visual-tests-netlify.yaml b/.github/workflows/shared-component-visual-tests-netlify.yaml index 1f9ae76826..e4b830406d 100644 --- a/.github/workflows/shared-component-visual-tests-netlify.yaml +++ b/.github/workflows/shared-component-visual-tests-netlify.yaml @@ -2,7 +2,9 @@ # It uploads the received images and diffs to netlify, printing the URLs to the console name: Upload Shared Component Visual Test Diffs on: - workflow_run: + # Privilege escalation necessary to deploy to Netlify + # 🚨 We must not execute any checked out code here. + workflow_run: # zizmor: ignore[dangerous-triggers] workflows: ["Shared Component Visual Tests"] types: - completed diff --git a/.github/workflows/shared-component-visual-tests.yaml b/.github/workflows/shared-component-visual-tests.yaml index 06b2d11b1c..1d01119b04 100644 --- a/.github/workflows/shared-component-visual-tests.yaml +++ b/.github/workflows/shared-component-visual-tests.yaml @@ -36,22 +36,10 @@ jobs: working-directory: packages/shared-components run: pnpm install --frozen-lockfile - - name: Get installed Playwright version - working-directory: packages/shared-components - id: playwright - run: echo "version=$(pnpm list @playwright/test --depth=0 --json | jq -r '.[].devDependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT - - - name: Cache playwright binaries - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 - id: playwright-cache + - name: Setup playwright + uses: ./.github/actions/setup-playwright with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell - - - name: Install Playwright browsers - working-directory: packages/shared-components - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: "pnpm playwright install --with-deps --only-shell" + write-cache: ${{ github.event_name != 'merge_group' }} - name: Run Visual tests working-directory: packages/shared-components @@ -65,7 +53,7 @@ jobs: - name: Upload received images & diffs if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: received-images path: packages/shared-components/__vis__/linux diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 73efd48ba3..e934f05ad1 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,6 +1,8 @@ name: SonarQube on: - workflow_run: + # Privilege escalation necessary to call upon SonarCloud + # 🚨 We must not execute any checked out code here. + workflow_run: # zizmor: ignore[dangerous-triggers] workflows: ["Tests"] types: - completed diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index f3052ff373..51d7926b83 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -5,8 +5,6 @@ on: branches: [develop, master] merge_group: types: [checks_requested] - repository_dispatch: - types: [element-web-notify] concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: true @@ -89,7 +87,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 i18n: strategy: @@ -127,7 +125,9 @@ jobs: # Dummy job to simplify branch protections ci: name: Static Analysis - needs: [lint, i18n] + needs: [lint, i18n, zizmor] + if: always() runs-on: ubuntu-24.04 steps: - - run: echo "Ok" + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b9ffa23c99..4b0013f5cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,8 +5,6 @@ on: types: [checks_requested] push: branches: [develop, master] - repository_dispatch: - types: [element-web-notify] workflow_call: inputs: disable_coverage: @@ -58,7 +56,7 @@ jobs: JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }} - name: Jest Cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: /tmp/jest_cache key: ${{ hashFiles('**/pnpm-lock.yaml') }} @@ -93,7 +91,7 @@ jobs: - name: Upload Artifact if: env.ENABLE_COVERAGE == 'true' - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: coverage-${{ matrix.runner }} path: | @@ -102,7 +100,7 @@ jobs: complete: name: jest-tests - needs: [jest_ew, vitest_sc] + needs: [jest_ew, vitest] if: always() runs-on: ubuntu-24.04 permissions: @@ -122,8 +120,13 @@ jobs: sha: ${{ github.sha }} target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - vitest_sc: - name: Vitest (Shared Components) + vitest: + name: Vitest + strategy: + matrix: + package: + - shared-components + - module-api runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -139,44 +142,32 @@ jobs: node-version: "lts/*" cache: "pnpm" - - name: Install Shared Component Deps - working-directory: "packages/shared-components" + - name: Install Deps run: "pnpm install" - name: Cache storybook & vitest - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | - packages/shared-components/node_modules/.cache - packages/shared-components/node_modules/.vite/vitest + packages/${{ matrix.package }}/node_modules/.cache + packages/${{ matrix.package }}/node_modules/.vite/vitest key: ${{ hashFiles('pnpm-lock.yaml') }} - - name: Get installed Playwright version - working-directory: packages/shared-components - id: playwright - run: echo "version=$(pnpm list @playwright/test --depth=0 --json | jq -r '.[].devDependencies["@playwright/test"].version')" >> $GITHUB_OUTPUT - - - name: Cache playwright binaries - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 - id: playwright-cache + - name: Setup playwright + uses: ./.github/actions/setup-playwright + if: matrix.package == 'shared-components' with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell - - - name: Install Playwright browsers - working-directory: packages/shared-components - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: "pnpm playwright install --with-deps --only-shell" + write-cache: ${{ github.event_name != 'merge_group' }} - name: Run tests - working-directory: "packages/shared-components" + working-directory: "packages/${{ matrix.package }}" run: pnpm test:unit --coverage=$ENABLE_COVERAGE - name: Upload Artifact if: env.ENABLE_COVERAGE == 'true' - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: coverage-sharedcomponents + name: coverage-${{ matrix.package }} path: | - packages/shared-components/coverage - !packages/shared-components/coverage/lcov-report + packages/${{ matrix.package }}/coverage + !packages/${{ matrix.package }}/coverage/lcov-report diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 496cfd53df..582207dc14 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -27,7 +27,7 @@ jobs: contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') || contains(github.event.issue.labels.*.name, 'A-Element-Call') steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | github.rest.issues.addLabels({ @@ -44,7 +44,7 @@ jobs: contains(github.event.issue.labels.*.name, 'good first issue') || contains(github.event.issue.labels.*.name, 'Hacktoberfest') steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | github.rest.issues.addLabels({ diff --git a/.github/workflows/triage-unlabelled.yml b/.github/workflows/triage-unlabelled.yml index 71396be804..04f312ab32 100644 --- a/.github/workflows/triage-unlabelled.yml +++ b/.github/workflows/triage-unlabelled.yml @@ -43,7 +43,7 @@ jobs: contains(github.event.issue.labels.*.name, 'A-Element-Call')) && contains(github.event.issue.labels.*.name, 'Z-Labs') steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | github.rest.issues.removeLabel({ diff --git a/.github/workflows/update-topics.yaml b/.github/workflows/update-topics.yaml index c1fb78e3b8..698c8da804 100644 --- a/.github/workflows/update-topics.yaml +++ b/.github/workflows/update-topics.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-24.04 environment: Matrix steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 env: HS_URL: ${{ secrets.BETABOT_HS_URL }} LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }} diff --git a/.prettierignore b/.prettierignore index ca5fe9afd8..b6f2c7a1e7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -14,7 +14,8 @@ webpack-stats.json .vscode/ .env coverage -# Auto-generated file +# Auto-generated files +*.api.md /apps/web/src/modules.ts /apps/web/src/modules.js src/i18n/strings diff --git a/CHANGELOG.md b/CHANGELOG.md index 78659f2037..4b87749adb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +Changes in [1.12.15](https://github.com/element-hq/element-web/releases/tag/v1.12.15) (2026-04-08) +================================================================================================== +Fixes Desktop release workflow. + +This release is identical to v1.12.14 otherwise. + +Changes in [1.12.14](https://github.com/element-hq/element-web/releases/tag/v1.12.14) (2026-04-07) +================================================================================================== +## ✨ Features + +* Add analytics tracking for URL previews ([#32659](https://github.com/element-hq/element-web/pull/32659)). Contributed by @Half-Shot. +* Collapsible Room List - Clicking on separator should expand to last set width ([#32909](https://github.com/element-hq/element-web/pull/32909)). Contributed by @MidhunSureshR. +* RoomList: improve performance ([#32919](https://github.com/element-hq/element-web/pull/32919)). Contributed by @florianduros. +* Implement collapsible panels for the new room list ([#32742](https://github.com/element-hq/element-web/pull/32742)). Contributed by @MidhunSureshR. +* Hide the names of banned users behind a spoiler tag (attempt 2) ([#32636](https://github.com/element-hq/element-web/pull/32636)). Contributed by @andybalaam. + +## 🐛 Bug Fixes + +* Use the code signing Subject Name as basis for Tray GUID on Windows ([#32939](https://github.com/element-hq/element-web/pull/32939)). Contributed by @t3chguy. +* Ensure the incoming verification request appears above the please verify prompt ([#32931](https://github.com/element-hq/element-web/pull/32931)). Contributed by @andybalaam. +* Collapsible Room List - Prevent any interaction with the separator when the panel is expanded ([#32910](https://github.com/element-hq/element-web/pull/32910)). Contributed by @MidhunSureshR. +* Fix icon size of badges in right panel ([#32952](https://github.com/element-hq/element-web/pull/32952)). Contributed by @florianduros. +* Fix room list often showing the wrong icons for calls ([#32881](https://github.com/element-hq/element-web/pull/32881)). Contributed by @robintown. +* Fix emoticon slash commands including stale buffers ([#32928](https://github.com/element-hq/element-web/pull/32928)). Contributed by @t3chguy. +* Fix presence indicators not showing without cache ([#32880](https://github.com/element-hq/element-web/pull/32880)). Contributed by @DLCSharp. +* Show space name instead of 'Empty room' after creation ([#32886](https://github.com/element-hq/element-web/pull/32886)). Contributed by @gugaribeiro05. +* Strip ephemeral query params from OIDC redirect URI ([#32875](https://github.com/element-hq/element-web/pull/32875)). Contributed by @azmeuk. + + Changes in [1.12.13](https://github.com/element-hq/element-web/releases/tag/v1.12.13) (2026-03-24) ================================================================================================== ## 🦖 Deprecations diff --git a/apps/desktop/.node-version b/apps/desktop/.node-version index d845d9d88d..8e35034890 100644 --- a/apps/desktop/.node-version +++ b/apps/desktop/.node-version @@ -1 +1 @@ -24.14.0 +24.14.1 diff --git a/apps/desktop/dockerbuild/Dockerfile b/apps/desktop/dockerbuild/Dockerfile index 9cdd24550d..0acea0e60d 100644 --- a/apps/desktop/dockerbuild/Dockerfile +++ b/apps/desktop/dockerbuild/Dockerfile @@ -1,7 +1,7 @@ # Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31) # with broader compatibility, down to Debian bullseye & Ubuntu focal. -FROM rust:bullseye@sha256:16950191527a4cb9e0762d9d48b705a6315158e4035e64f7a93ce8656a1b053c +FROM rust:bullseye@sha256:bc19574c121fe10c1bc68fc2b1ea9b420d87d047a0c50fb1622b282199700cee ENV DEBIAN_FRONTEND=noninteractive diff --git a/apps/desktop/hak/matrix-seshat/check.ts b/apps/desktop/hak/matrix-seshat/check.ts index a62a239c80..99a107eb5a 100644 --- a/apps/desktop/hak/matrix-seshat/check.ts +++ b/apps/desktop/hak/matrix-seshat/check.ts @@ -14,10 +14,7 @@ import type { Tool } from "../../scripts/hak/hakEnv.ts"; import type { DependencyInfo } from "../../scripts/hak/dep.ts"; export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise { - const tools: Tool[] = [ - ["rustc", "--version"], - ["python", "--version"], // node-gyp uses python for reasons beyond comprehension - ]; + const tools: Tool[] = [["rustc", "--version"]]; if (hakEnv.isWin()) { tools.push(["perl", "--version"]); // for openssl configure tools.push(["nasm", "-v"]); // for openssl building @@ -28,6 +25,14 @@ export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Prom } await hakEnv.checkTools(tools); + try { + // node-gyp uses python for reasons beyond comprehension + // Try python3 first, to get a more sensible error if python is not found in the fallback + await hakEnv.checkTools([["python3", "--version"]]); + } catch { + await hakEnv.checkTools([["python", "--version"]]); + } + // Ensure Rust target exists (nb. we avoid depending on rustup) await new Promise((resolve, reject) => { const rustc = childProcess.execFile( diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e43c10023e..eb63cd55ee 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -3,7 +3,7 @@ "productName": "Element", "main": "lib/electron-main.js", "exports": "./lib/electron-main.js", - "version": "1.12.13", + "version": "1.12.15", "description": "Element: the future of secure communication", "author": { "name": "Element", @@ -71,7 +71,7 @@ "@babel/core": "^7.18.10", "@babel/preset-env": "^7.18.10", "@babel/preset-typescript": "^7.18.6", - "@electron/asar": "4.1.0", + "@electron/asar": "4.1.2", "@playwright/test": "catalog:", "@stylistic/eslint-plugin": "^5.0.0", "@types/auto-launch": "^5.0.1", @@ -84,7 +84,7 @@ "app-builder-lib": "26.8.2", "chokidar": "^5.0.0", "detect-libc": "^2.0.0", - "electron": "41.0.3", + "electron": "41.1.0", "electron-builder": "26.8.2", "electron-builder-squirrel-windows": "26.8.2", "electron-devtools-installer": "^4.0.0", @@ -105,7 +105,7 @@ "typescript": "5.9.3" }, "hakDependencies": { - "matrix-seshat": "^4.0.1" + "matrix-seshat": "4.0.1" }, - "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be" + "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" } diff --git a/apps/desktop/playwright.config.ts b/apps/desktop/playwright.config.ts index 6b3e76347f..bb8e2b52b4 100644 --- a/apps/desktop/playwright.config.ts +++ b/apps/desktop/playwright.config.ts @@ -8,25 +8,9 @@ Please see LICENSE files in the repository root for full details. import { defineConfig } from "@playwright/test"; -const projects = [ - "macos", - "win-x64", - "win-ia32", - "win-arm64", - "linux-amd64-sqlcipher-system", - "linux-amd64-sqlcipher-static", - "linux-arm64-sqlcipher-system", - "linux-arm64-sqlcipher-static", -]; - export default defineConfig({ - // Allows the GitHub action to specify a project name (OS + arch) for the combined report to make sense - // workaround for https://github.com/microsoft/playwright/issues/33521 - projects: process.env.CI - ? projects.map((name) => ({ - name, - })) - : undefined, + projects: [{ name: "Desktop" }], + tag: process.env.PW_TAG ? `@${process.env.PW_TAG}` : undefined, use: { viewport: { width: 1280, height: 720 }, video: "retain-on-failure", diff --git a/apps/desktop/playwright/Dockerfile b/apps/desktop/playwright/Dockerfile index e2fa600efc..dabf89dd46 100644 --- a/apps/desktop/playwright/Dockerfile +++ b/apps/desktop/playwright/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.58.2-jammy@sha256:4698a73749c5848d3f5fcd42a2174d172fcad2b2283e087843b115424303a565 +FROM mcr.microsoft.com/playwright:v1.59.1-jammy@sha256:8a0360d39d1973be506dd59002904a774f6d697d4946c94063b3fd006461c8ff WORKDIR /work/element-desktop diff --git a/apps/desktop/playwright/snapshots/launch/launch.spec.ts/App-launch-should-launch-and-render-the-welcome-view-successfully-1-linux.png b/apps/desktop/playwright/snapshots/launch/launch.spec.ts/App-launch-should-launch-and-render-the-welcome-view-successfully-1-linux.png index f0612be6e2..7bcf260d52 100644 Binary files a/apps/desktop/playwright/snapshots/launch/launch.spec.ts/App-launch-should-launch-and-render-the-welcome-view-successfully-1-linux.png and b/apps/desktop/playwright/snapshots/launch/launch.spec.ts/App-launch-should-launch-and-render-the-welcome-view-successfully-1-linux.png differ diff --git a/apps/desktop/project.json b/apps/desktop/project.json index bf186a1386..cb43a99d61 100644 --- a/apps/desktop/project.json +++ b/apps/desktop/project.json @@ -1,6 +1,7 @@ { "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "app", + "projectType": "application", + "implicitDependencies": ["element-web"], "root": "apps/desktop", "targets": { "docker:build": { diff --git a/apps/desktop/scripts/branch-match.sh b/apps/desktop/scripts/branch-match.sh deleted file mode 100755 index c42073dfa4..0000000000 --- a/apps/desktop/scripts/branch-match.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Script for downloading a branch of element-web matching the branch a PR is contributed from - -set -x - -deforg="element-hq" -defrepo="element-web" - -# The PR_NUMBER variable must be set explicitly. -default_org_repo=${GITHUB_REPOSITORY:-"$deforg/$defrepo"} -PR_ORG=${PR_ORG:-${default_org_repo%%/*}} -PR_REPO=${PR_REPO:-${default_org_repo##*/}} - -# A function that clones a branch of a repo based on the org, repo and branch -clone() { - org=$1 - repo=$2 - branch=$3 - if [ -n "$branch" ] - then - echo "Trying to use $org/$repo#$branch" - # Disable auth prompts: https://serverfault.com/a/665959 - GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0 - fi -} - -echo "Getting info about a PR with number $PR_NUMBER" -apiEndpoint="https://api.github.com/repos/$PR_ORG/$PR_REPO/pulls/$PR_NUMBER" -head=$(curl "$apiEndpoint" | jq -r '.head.label') - -# for forks, $head will be in the format "fork:branch", so we split it by ":" -# into an array. On non-forks, this has the effect of splitting into a single -# element array given ":" shouldn't appear in the head - it'll just be the -# branch name. Based on the results, we clone. -BRANCH_ARRAY=(${head//:/ }) -TRY_ORG=$deforg -TRY_BRANCH=${BRANCH_ARRAY[0]} -if [[ "$head" == *":"* ]]; then - # ... but only match that fork if it's a real fork - if [ "${BRANCH_ARRAY[0]}" != "$PR_ORG" ]; then - TRY_ORG=${BRANCH_ARRAY[0]} - fi - TRY_BRANCH=${BRANCH_ARRAY[1]} -fi -clone "$TRY_ORG" "$defrepo" "$TRY_BRANCH" - -exit 1 diff --git a/apps/desktop/scripts/hak/hakEnv.ts b/apps/desktop/scripts/hak/hakEnv.ts index edab24419b..4ce382dc11 100644 --- a/apps/desktop/scripts/hak/hakEnv.ts +++ b/apps/desktop/scripts/hak/hakEnv.ts @@ -103,6 +103,9 @@ export default class HakEnv { shell: this.isWin(), ...options, }); + proc.on("error", (err) => { + reject(err); + }); proc.on("exit", (code) => { if (code) { reject(code); diff --git a/apps/web/.stylelintrc.cjs b/apps/web/.stylelintrc.cjs index 57c39e8c05..2ccb2abf75 100644 --- a/apps/web/.stylelintrc.cjs +++ b/apps/web/.stylelintrc.cjs @@ -56,6 +56,7 @@ module.exports = { { from: "res/css/views/rooms/_EditMessageComposer.pcss", type: "css" }, { from: "res/css/views/right_panel/_BaseCard.pcss", type: "css" }, { from: "res/css/views/messages/_MessageActionBar.pcss", type: "css" }, + { from: "res/css/views/messages/_ThreadActionBar.pcss", type: "css" }, { from: "res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss", type: "css" }, { from: "res/css/views/elements/_ToggleSwitch.pcss", type: "css" }, { from: "res/css/views/settings/tabs/_SettingsTab.pcss", type: "css" }, diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 7cc187024b..ca8d04950d 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -2,7 +2,7 @@ # Context must be the root of the monorepo # Builder -FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:4bfbd78e049926e4ca595c1798810691ca7bb5aedd829ffd8a78b2ab30689810 AS builder +FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:27e462f5db2402700867dfa8ec35e3a68b127fdf61b505db0dd6ab98c38284bb AS builder # Support custom branch of the js-sdk. This also helps us build images of element-web develop. ARG USE_CUSTOM_SDKS=false @@ -25,7 +25,7 @@ RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-package.s RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json # App -FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:4011c42f28e9b54c86b52211598dbc6bcaa520311ddd55f211587cdd71f88a9c +FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:b5831ee7f7aa827cbae87df4a30a642f62c747d8525f5674365389f3adab278d # Need root user to install packages & manipulate the usr directory USER root diff --git a/apps/web/Dockerfile.dockerignore b/apps/web/Dockerfile.dockerignore index 403d667eaa..c1af93fc7e 100644 --- a/apps/web/Dockerfile.dockerignore +++ b/apps/web/Dockerfile.dockerignore @@ -10,6 +10,7 @@ **/.pnpm-store **/tsconfig.node.tsbuildinfo **/*.md +!**/*.api.md **/*.rst .idea/ diff --git a/apps/web/jest.config.ts b/apps/web/jest.config.ts index 445e9439fd..5d3d16f464 100644 --- a/apps/web/jest.config.ts +++ b/apps/web/jest.config.ts @@ -46,7 +46,7 @@ const config: Config = { "@vector-im/compound-web": "/../../node_modules/@vector-im/compound-web", }, transformIgnorePatterns: [ - "/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp|matrix-web-i18n|await-lock|@element-hq/web-shared-components|react-virtuoso|lodash)).+$", + "/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp|matrix-web-i18n|await-lock|@element-hq/web-shared-components|react-virtuoso|lodash|domutils|domhandler|domelementtype|dom-serializer|entities)).+$", ], collectCoverageFrom: [ "/src/**/*.{js,ts,tsx}", diff --git a/apps/web/package.json b/apps/web/package.json index 956cad5607..8d4a2edd05 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.12.13", + "version": "1.12.15", "description": "Element: the future of secure communication", "author": "New Vector Ltd.", "repository": { @@ -30,15 +30,15 @@ "lint:types": "nx lint:types", "lint:style": "stylelint \"res/css/**/*.pcss\"", "test": "nx test:unit", - "test:playwright": "playwright test", - "test:playwright:open": "pnpm test:playwright --ui", - "test:playwright:screenshots": "playwright-screenshots-experimental pnpm playwright test --update-snapshots --project=Chrome --grep @screenshot", + "test:playwright": "nx test:playwright --", + "test:playwright:open": "nx test:playwright -- --ui", + "test:playwright:screenshots": "nx test:playwright:screenshots --", "coverage": "pnpm test --coverage", "analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp" }, "dependencies": { "@babel/runtime": "^7.12.5", - "@element-hq/element-web-module-api": "catalog:", + "@element-hq/element-web-module-api": "workspace:*", "@element-hq/web-shared-components": "workspace:*", "@fontsource/fira-code": "^5", "@fontsource/inter": "catalog:", @@ -63,11 +63,11 @@ "css-tree": "^3.0.0", "diff-dom": "^5.0.0", "diff-match-patch": "^1.0.5", - "domutils": "^3.2.2", + "domutils": "^4.0.0", "emojibase-regex": "^17.0.0", "escape-html": "^1.0.3", "file-saver": "^2.0.5", - "filesize": "11.0.13", + "filesize": "11.0.15", "github-markdown-css": "^5.5.1", "glob-to-regexp": "^0.4.1", "highlight.js": "^11.3.1", @@ -78,7 +78,7 @@ "jsrsasign": "^11.0.0", "jszip": "^3.7.0", "katex": "^0.16.0", - "lodash": "npm:lodash-es@^4.17.21", + "lodash": "npm:lodash-es@4.18.1", "maplibre-gl": "^5.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#hs/profile-sync-endpoints", @@ -89,7 +89,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.360.2", + "posthog-js": "1.364.7", "qrcode": "1.5.4", "re-resizable": "6.11.2", "react": "catalog:", @@ -101,7 +101,7 @@ "react-transition-group": "^4.4.1", "rfc4648": "^1.4.0", "sanitize-filename": "^1.6.3", - "sanitize-html": "2.17.1", + "sanitize-html": "2.17.2", "tar-js": "^0.3.0", "ua-parser-js": "1.0.40", "uuid": "^13.0.0", @@ -125,10 +125,9 @@ "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", "@babel/preset-typescript": "^7.12.7", - "@casualbot/jest-sonar-reporter": "2.5.0", + "@casualbot/jest-sonar-reporter": "2.5.1", "@element-hq/element-call-embedded": "0.18.0", - "@element-hq/element-web-playwright-common": "catalog:", - "@element-hq/element-web-playwright-common-local": "workspace:*", + "@element-hq/element-web-playwright-common": "workspace:*", "@fetch-mock/jest": "^0.2.20", "@jest/globals": "^30.2.0", "@peculiar/webcrypto": "^1.4.3", @@ -203,7 +202,7 @@ "jest-raw-loader": "^1.0.1", "jsqr": "^1.4.0", "matrix-web-i18n": "catalog:", - "mini-css-extract-plugin": "2.10.1", + "mini-css-extract-plugin": "2.10.2", "modernizr": "^3.12.0", "playwright-core": "catalog:", "postcss": "8.5.8", @@ -246,6 +245,6 @@ "engines": { "node": ">=22.18" }, - "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be", + "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", "private": true } diff --git a/apps/web/playwright/e2e/crypto/device-verification.spec.ts b/apps/web/playwright/e2e/crypto/device-verification.spec.ts index 1a1731e6ae..07fa4ed9d8 100644 --- a/apps/web/playwright/e2e/crypto/device-verification.spec.ts +++ b/apps/web/playwright/e2e/crypto/device-verification.spec.ts @@ -21,7 +21,6 @@ import { waitForVerificationRequest, } from "./utils"; import { type Bot } from "../../pages/bot"; -import { Toasts } from "../../pages/toasts.ts"; import type { ElementAppPage } from "../../pages/ElementAppPage.ts"; test.describe("Device verification", { tag: "@no-webkit" }, () => { @@ -82,7 +81,11 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { ); // 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 }) => { + test("No toast after verification, even if the secrets take a while to arrive", async ({ + page, + credentials, + toasts, + }) => { // 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({ @@ -121,7 +124,6 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { 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(); diff --git a/apps/web/playwright/e2e/crypto/toasts.spec.ts b/apps/web/playwright/e2e/crypto/toasts.spec.ts index 876000009b..9ce1b8a5ae 100644 --- a/apps/web/playwright/e2e/crypto/toasts.spec.ts +++ b/apps/web/playwright/e2e/crypto/toasts.spec.ts @@ -43,11 +43,7 @@ test.describe("Key storage out of sync toast", () => { }); test("should prompt for recovery key if 'enter recovery key' pressed", { tag: "@screenshot" }, async ({ page }) => { - // We need to wait for there to be two toasts as the wait below won't work in isolation: - // playwright only evaluates the 'first()' call initially, not subsequent times it checks, so - // it would always be checking the same toast, even if another one is now the first. - await expect(page.getByRole("alert")).toHaveCount(2); - await expect(page.getByRole("alert").first()).toMatchScreenshot( + await expect(page.getByRole("alert").filter({ hasText: "Your key storage is out of sync." })).toMatchScreenshot( "key-storage-out-of-sync-toast.png", screenshotOptions, ); diff --git a/apps/web/playwright/e2e/devtools/lowbandwidth.spec.ts b/apps/web/playwright/e2e/devtools/lowbandwidth.spec.ts new file mode 100644 index 0000000000..d24ac69b94 --- /dev/null +++ b/apps/web/playwright/e2e/devtools/lowbandwidth.spec.ts @@ -0,0 +1,61 @@ +/* +Copyright 2026 Element Creations 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 { getSampleFilePath } from "../../sample-files"; + +test.describe("Devtools", () => { + test.use({ + displayName: "Alice", + }); + + test("should allow enabling low bandwidth mode", async ({ page, homeserver, user, app }) => { + // Upload a picture + const userSettings = await app.settings.openUserSettings("Account"); + const profileSettings = userSettings.locator(".mx_UserProfileSettings"); + await profileSettings.getByAltText("Upload").setInputFiles(getSampleFilePath("riot.png")); + await app.closeDialog(); + + // Create an initial room. + const createRoomDialog = await app.openCreateRoomDialog(); + await createRoomDialog.getByRole("textbox", { name: "Name" }).fill("Test Room"); + await createRoomDialog.getByRole("button", { name: "Create room" }).click(); + + const composer = app.getComposer().locator("[contenteditable]"); + await composer.fill("/devtools"); + await composer.press("Enter"); + const dialog = page.locator(".mx_Dialog"); + await dialog.getByLabel("Developer mode").check(); + await dialog.getByLabel("Disable bandwidth-heavy features").click(); + // Wait for refresh. + await page.waitForEvent("domcontentloaded"); + await app.viewRoomByName("Test Room"); + + // This only appears when encryption has been disabled in the client. + await expect(page.getByText("The encryption used by this room isn't supported.")).toBeVisible(); + + // None of these should be requested. + let hasSentTyping = false; + let hasRequestedThumbnail = false; + await page.route("**/_matrix/client/v3/rooms/*/typing/*", async (route) => { + hasSentTyping = true; + await route.fulfill({ json: {} }); + }); + await page.route("**/_matrix/media/v3/thumbnail/**", async (route) => { + hasRequestedThumbnail = true; + await route.fulfill({ json: {} }); + }); + await page.route("**/_matrix/client/v1/media/thumbnail/**", async (route) => { + hasRequestedThumbnail = true; + await route.fulfill({ json: {} }); + }); + + await composer.pressSequentially("Provoke typing request", { delay: 5 }); + expect(hasSentTyping).toEqual(false); + expect(hasRequestedThumbnail).toEqual(false); + }); +}); diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts new file mode 100644 index 0000000000..9bc9bbe2b0 --- /dev/null +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts @@ -0,0 +1,288 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { type Locator, type Page } from "@playwright/test"; + +import { expect, test } from "../../../element-web-test"; + +test.describe("Room list sections", () => { + test.use({ + displayName: "Alice", + labsFlags: ["feature_new_room_list", "feature_room_list_sections"], + botCreateOpts: { + displayName: "BotBob", + autoAcceptInvites: true, + }, + }); + + /** + * Get the room list + * @param page + */ + function getRoomList(page: Page): Locator { + return page.getByTestId("room-list"); + } + + /** + * Get the primary filters + * @param page + */ + function getPrimaryFilters(page: Page): Locator { + return page.getByTestId("primary-filters"); + } + + /** + * Get a section header toggle button by section name + * @param page + * @param sectionName The display name of the section (e.g. "Favourites", "Chats", "Low Priority") + * @param isUnread Whether to look for the unread version of the section header + */ + function getSectionHeader(page: Page, sectionName: string, isUnread = false): Locator { + return getRoomList(page).getByRole("gridcell", { + name: isUnread ? `Toggle ${sectionName} section with unread room(s)` : `Toggle ${sectionName} section`, + }); + } + + test.beforeEach(async ({ page, app, user }) => { + // The notification toast is displayed above the search section + await app.closeNotificationToast(); + + // focus the user menu to avoid to have hover decoration + await page.getByRole("button", { name: "User menu" }).focus(); + }); + + test.describe("Section rendering", () => { + test.beforeEach(async ({ app, user }) => { + // Create regular rooms + for (let i = 0; i < 3; i++) { + await app.client.createRoom({ name: `room${i}` }); + } + }); + + test("should render sections with correct rooms in each", { tag: "@screenshot" }, async ({ page, app }) => { + // Create a favourite room + const favouriteId = await app.client.createRoom({ name: "favourite room" }); + await app.client.evaluate(async (client, roomId) => { + await client.setRoomTag(roomId, "m.favourite"); + }, favouriteId); + + // Create a low priority room + const lowPrioId = await app.client.createRoom({ name: "low prio room" }); + await app.client.evaluate(async (client, roomId) => { + await client.setRoomTag(roomId, "m.lowpriority"); + }, lowPrioId); + + const roomList = getRoomList(page); + + // All three section headers should be visible + await expect(getSectionHeader(page, "Favourites")).toBeVisible(); + await expect(getSectionHeader(page, "Chats")).toBeVisible(); + await expect(getSectionHeader(page, "Low Priority")).toBeVisible(); + + // Ensure all rooms are visible + await expect(roomList.getByRole("row", { name: "Open room favourite room" })).toBeVisible(); + await expect(roomList.getByRole("row", { name: "Open room low prio room" })).toBeVisible(); + await expect(roomList.getByRole("row", { name: "Open room room0" })).toBeVisible(); + + await expect(roomList).toMatchScreenshot("room-list-sections.png"); + }); + + test("should only show non-empty sections", async ({ page, app }) => { + // No low priority rooms created, only regular and favourite rooms + const favouriteId = await app.client.createRoom({ name: "favourite room" }); + await app.client.evaluate(async (client, roomId) => { + await client.setRoomTag(roomId, "m.favourite"); + }, favouriteId); + + // Chats and Favourites sections should still be visible + await expect(getSectionHeader(page, "Chats")).toBeVisible(); + await expect(getSectionHeader(page, "Favourites")).toBeVisible(); + // Low Priority sections should not be visible + await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible(); + }); + + test("should render a flat list when there is only rooms in Chats section", async ({ page, app }) => { + // All sections should not be visible + await expect(getSectionHeader(page, "Chats")).not.toBeVisible(); + await expect(getSectionHeader(page, "Favourites")).not.toBeVisible(); + await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible(); + // It should be a flat list (using listbox a11y role) + await expect(page.getByRole("listbox", { name: "Room list", exact: true })).toBeVisible(); + await expect(getRoomList(page).getByRole("option", { name: "Open room room0" })).toBeVisible(); + }); + }); + + test.describe("Section collapse and expand", () => { + [ + { section: "Favourites", roomName: "favourite room", tag: "m.favourite" }, + { section: "Low Priority", roomName: "low prio room", tag: "m.lowpriority" }, + ].forEach(({ section, roomName, tag }) => { + test(`should collapse and expand the ${section} section`, async ({ page, app }) => { + const roomId = await app.client.createRoom({ name: roomName }); + if (tag) { + await app.client.evaluate( + async (client, { roomId, tag }) => { + await client.setRoomTag(roomId, tag); + }, + { roomId, tag }, + ); + } + + const roomList = getRoomList(page); + const sectionHeader = getSectionHeader(page, section); + + // The room should be visible + await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible(); + + // Collapse the section + await sectionHeader.click(); + + // The room should no longer be visible + await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).not.toBeVisible(); + + // The section header should still be visible + await expect(sectionHeader).toBeVisible(); + + // Expand the section again + await sectionHeader.click(); + + // The room should be visible again + await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible(); + }); + }); + + test("should render collapsed section", { tag: "@screenshot" }, async ({ page, app }) => { + const favouriteId = await app.client.createRoom({ name: "favourite room" }); + await app.client.evaluate(async (client, roomId) => { + await client.setRoomTag(roomId, "m.favourite"); + }, favouriteId); + + await app.client.createRoom({ name: "regular room" }); + + const roomList = getRoomList(page); + + // Collapse the Favourites section + await getSectionHeader(page, "Favourites").click(); + + // Verify favourite room is hidden but regular room is still visible + await expect(roomList.getByRole("row", { name: "Open room favourite room" })).not.toBeVisible(); + await expect(roomList.getByRole("row", { name: "Open room regular room" })).toBeVisible(); + + await expect(roomList).toMatchScreenshot("room-list-sections-collapsed.png"); + }); + }); + + test.describe("Rooms placement in sections", () => { + test("should move a room between sections when tags change", async ({ page, app }) => { + await app.client.createRoom({ name: "my room" }); + + const roomList = getRoomList(page); + + // Flat list because there is only rooms in the Chats section + let roomItem = roomList.getByRole("option", { name: "Open room my room" }); + await expect(roomItem).toBeVisible(); + + // Favourite the room via context menu + await roomItem.click({ button: "right" }); + await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click(); + + // The Favourites section header should now be visible and the room should be under it + await expect(getSectionHeader(page, "Favourites")).toBeVisible(); + roomItem = roomList.getByRole("row", { name: "Open room my room" }); + await expect(roomItem).toBeVisible(); + + // Unfavourite the room + await roomItem.hover(); + await roomItem.getByRole("button", { name: "More Options" }).click(); + await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click(); + + // Mark the room as low priority via context menu + roomItem = roomList.getByRole("option", { name: "Open room my room" }); + await roomItem.click({ button: "right" }); + await page.getByRole("menuitemcheckbox", { name: "Low priority" }).click(); + + // The Low Priority section header should now be visible and the room should be under it + await expect(getSectionHeader(page, "Low Priority")).toBeVisible(); + roomItem = roomList.getByRole("row", { name: "Open room my room" }); + await expect(roomItem).toBeVisible(); + }); + }); + + test("should show unread indicator on section header", async ({ page, app, bot }) => { + // Create a favourite room + const favouriteId = await app.client.createRoom({ name: "favourite room" }); + await app.client.evaluate(async (client, roomId) => { + await client.setRoomTag(roomId, "m.favourite"); + }, favouriteId); + + const roomList = getRoomList(page); + + // Invite the bot and have it send a message to generate an unread + await app.client.inviteUser(favouriteId, bot.credentials.userId); + await bot.joinRoom(favouriteId); + await bot.sendMessage(favouriteId, "Hello from bot!"); + + let sectionHeader = getSectionHeader(page, "Favourites", true); + await expect(sectionHeader).toBeVisible(); + + // Open the room to mark it as read + await roomList.getByRole("row", { name: "Open room favourite room" }).click(); + + // The section should no longer be unread + sectionHeader = getSectionHeader(page, "Favourites", false); + await expect(sectionHeader).toBeVisible(); + }); + + test.describe("Sections and filters interaction", () => { + test("should not show Favourite and Low Priority filters when sections are enabled", async ({ page, app }) => { + const primaryFilters = getPrimaryFilters(page); + + // Expand the filter list to see all filters + const expandButton = primaryFilters.getByRole("button", { name: "Expand filter list" }); + await expandButton.click(); + + // Favourite and Low Priority filters should NOT be visible since sections handle them + await expect(primaryFilters.getByRole("option", { name: "Favourite" })).not.toBeVisible(); + + // Other filters should still be present + await expect(primaryFilters.getByRole("option", { name: "People" })).toBeVisible(); + await expect(primaryFilters.getByRole("option", { name: "Rooms" })).toBeVisible(); + await expect(primaryFilters.getByRole("option", { name: "Unread" })).toBeVisible(); + }); + + test("should maintain sections when a filter is applied", async ({ page, app, bot }) => { + // Create a favourite room with unread messages + const favouriteId = await app.client.createRoom({ name: "fav with unread" }); + await app.client.evaluate(async (client, roomId) => { + await client.setRoomTag(roomId, "m.favourite"); + }, favouriteId); + await app.client.inviteUser(favouriteId, bot.credentials.userId); + await bot.joinRoom(favouriteId); + await bot.sendMessage(favouriteId, "Hello from favourite!"); + + // Create a regular room with unread messages + const regularId = await app.client.createRoom({ name: "regular with unread" }); + await app.client.inviteUser(regularId, bot.credentials.userId); + await bot.joinRoom(regularId); + await bot.sendMessage(regularId, "Hello from regular!"); + + // Create a room without unread + await app.client.createRoom({ name: "no unread room" }); + + const roomList = getRoomList(page); + const primaryFilters = getPrimaryFilters(page); + + // Apply the Unread filter + await primaryFilters.getByRole("option", { name: "Unread" }).click(); + + // Only rooms with unreads should be visible + await expect(roomList.getByRole("row", { name: "fav with unread" })).toBeVisible(); + await expect(roomList.getByRole("row", { name: "regular with unread" })).toBeVisible(); + await expect(roomList.getByRole("row", { name: "no unread room" })).not.toBeVisible(); + }); + }); +}); diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index 5907ad6d97..79cc0b4cf3 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -328,11 +328,11 @@ test.describe("Room list", () => { const roomListView = getRoomList(page); const videoRoom = roomListView.getByRole("option", { name: "video room" }); + await expect(videoRoom).toHaveAttribute("aria-selected", "true"); // wait for room list update // focus the user menu to avoid to have hover decoration await page.getByRole("button", { name: "User menu" }).focus(); - await expect(videoRoom).toBeVisible(); await expect(videoRoom).toMatchScreenshot("room-list-item-video.png"); }); }); diff --git a/apps/web/playwright/e2e/links/messages.spec.ts b/apps/web/playwright/e2e/links/messages.spec.ts index 1f86e1bc09..74af6c834e 100644 --- a/apps/web/playwright/e2e/links/messages.spec.ts +++ b/apps/web/playwright/e2e/links/messages.spec.ts @@ -14,7 +14,7 @@ test.describe("Message links", () => { await use({ roomId }); }, }); - for (const link of ["https://example.org", "example.org", "ftp://example.org"]) { + for (const link of ["https://example.org", "ftp://example.org"]) { test(`should linkify a regular link '${link}'`, async ({ page, user, app, room }) => { await page.goto(`#/room/${room.roomId}`); // Needs to be unformatted so we test linkifing @@ -24,6 +24,13 @@ test.describe("Message links", () => { await expect(linkElement).toBeVisible(); }); } + test("should NOT linkify a bare domain", async ({ page, user, app, room }) => { + await page.goto(`#/room/${room.roomId}`); + // Needs to be unformatted so we test linkifing + await app.client.sendMessage(room.roomId, `Check out example.org`); + const linkElement = page.locator(".mx_EventTile_last").getByRole("link", { name: "example.org" }); + await expect(linkElement).not.toBeVisible(); + }); test("should linkify a User ID", async ({ page, user, app, room }) => { await page.goto(`#/room/${room.roomId}`); // Needs to be unformatted so we test linkifing diff --git a/apps/web/playwright/e2e/links/topic.spec.ts b/apps/web/playwright/e2e/links/topic.spec.ts index f6574860cd..fff866119d 100644 --- a/apps/web/playwright/e2e/links/topic.spec.ts +++ b/apps/web/playwright/e2e/links/topic.spec.ts @@ -14,13 +14,7 @@ test.describe("Topic links", () => { await use({ roomId }); }, }); - for (const link of [ - "https://example.org", - "example.org", - "ftp://example.org", - "#aroom:example.org", - "@alice:example.org", - ]) { + for (const link of ["https://example.org", "ftp://example.org", "#aroom:example.org", "@alice:example.org"]) { // Playwright treats '@' as a tag, so replace it to be safe test(`should linkify plaintext '${link.replace("@", "_@")}'`, async ({ page, user, app, room }) => { await app.client.sendStateEvent( diff --git a/apps/web/playwright/e2e/room-directory/room-directory.spec.ts b/apps/web/playwright/e2e/room-directory/room-directory.spec.ts index 741fde3505..6eea5abce7 100644 --- a/apps/web/playwright/e2e/room-directory/room-directory.spec.ts +++ b/apps/web/playwright/e2e/room-directory/room-directory.spec.ts @@ -48,9 +48,9 @@ test.describe("Room Directory", () => { await app.closeDialog(); const resp = await bot.publicRooms({}); - expect(resp.total_room_count_estimate).toEqual(1); - expect(resp.chunk).toHaveLength(1); - expect(resp.chunk[0].room_id).toEqual(roomId); + expect(resp.total_room_count_estimate).toBeGreaterThanOrEqual(1); + expect(resp.chunk).toHaveLength(resp.total_room_count_estimate); + expect(resp.chunk.find((r) => r.room_id === roomId)).toBeTruthy(); }, ); diff --git a/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts index 07c3a9592d..b3ec952b01 100644 --- a/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts @@ -24,7 +24,7 @@ test.describe("Account user settings tab", () => { }, }); - test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user }) => { + test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user, axe }) => { await expect(uut).toMatchScreenshot("account.png"); // Assert that the top heading is rendered @@ -70,6 +70,8 @@ test.describe("Account user settings tab", () => { await expect(accountManagementSection.getByRole("button", { name: "Deactivate Account" })).toHaveClass( /mx_AccessibleButton_kind_danger/, ); + + await expect(axe).toHaveNoViolations(); }); test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page, uut }) => { diff --git a/apps/web/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts index c6af256a5d..457c5c17cf 100644 --- a/apps/web/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts @@ -13,7 +13,7 @@ test.describe("Appearance user settings tab", () => { displayName: "Hanako", }); - test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app }) => { + test("should be rendered properly", { tag: "@screenshot" }, async ({ page, user, app, axe }) => { const tab = await app.settings.openUserSettings("Appearance"); // Click "Show advanced" link button @@ -23,6 +23,8 @@ test.describe("Appearance user settings tab", () => { await expect(tab.getByRole("button", { name: "Hide advanced" })).toBeVisible(); await expect(tab).toMatchScreenshot("appearance-tab.png"); + + await expect(axe).toHaveNoViolations(); }); test( diff --git a/apps/web/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts b/apps/web/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts index 8b7cf720c8..cb38f923e9 100644 --- a/apps/web/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts +++ b/apps/web/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts @@ -23,7 +23,7 @@ test.describe("Appearance user settings tab", () => { test( "should be rendered with the light theme selected", { tag: "@screenshot" }, - async ({ page, app, util }) => { + async ({ page, app, util, axe }) => { // Assert that 'Match system theme' is not checked await expect(util.getMatchSystemThemeSwitch()).not.toBeChecked(); @@ -34,6 +34,8 @@ test.describe("Appearance user settings tab", () => { await expect(util.getHighContrastTheme()).not.toBeChecked(); await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-light.png"); + + await expect(axe).toHaveNoViolations(); }, ); diff --git a/apps/web/playwright/e2e/settings/device-management.spec.ts b/apps/web/playwright/e2e/settings/device-management.spec.ts index 9f60b2433c..265ec9c877 100644 --- a/apps/web/playwright/e2e/settings/device-management.spec.ts +++ b/apps/web/playwright/e2e/settings/device-management.spec.ts @@ -23,7 +23,7 @@ test.describe("Device manager", () => { } }); - test("should display sessions", async ({ page, app }) => { + test("should display sessions", async ({ page, app, axe }) => { await app.settings.openUserSettings("Sessions"); const tab = page.locator(".mx_SettingsTab"); @@ -85,7 +85,7 @@ test.describe("Device manager", () => { // session name updated in details await expect(firstSession.locator(".mx_DeviceDetailHeading h4").getByText(sessionName)).toBeVisible(); // and main list item - await expect(firstSession.locator(".mx_DeviceTile h4").getByText(sessionName)).toBeVisible(); + await expect(firstSession.locator(".mx_DeviceTile h3").getByText(sessionName)).toBeVisible(); // sign out using the device details sign out await firstSession.getByRole("button", { name: "Remove this session" }).click(); @@ -96,5 +96,7 @@ test.describe("Device manager", () => { // no other sessions or security recommendations sections when only one session await expect(tab.getByText("Other sessions")).not.toBeVisible(); await expect(tab.getByTestId("security-recommendations-section")).not.toBeVisible(); + + await expect(axe).toHaveNoViolations(); }); }); diff --git a/apps/web/playwright/e2e/settings/encryption-user-tab/advanced.spec.ts b/apps/web/playwright/e2e/settings/encryption-user-tab/advanced.spec.ts index 1d8ae102fd..0167a321b7 100644 --- a/apps/web/playwright/e2e/settings/encryption-user-tab/advanced.spec.ts +++ b/apps/web/playwright/e2e/settings/encryption-user-tab/advanced.spec.ts @@ -16,7 +16,7 @@ test.describe("Advanced section in Encryption tab", () => { await bootstrapCrossSigningForClient(clientHandle, credentials, true); }); - test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util }) => { + test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util, axe }) => { await util.openEncryptionTab(); const section = util.getEncryptionDetailsSection(); @@ -26,6 +26,8 @@ test.describe("Advanced section in Encryption tab", () => { await expect(section).toMatchScreenshot("encryption-details.png", { mask: [section.getByTestId("deviceId"), section.getByTestId("sessionKey")], }); + + await expect(axe).toHaveNoViolations(); }); test("should show the import room keys dialog", async ({ page, app, util }) => { diff --git a/apps/web/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts b/apps/web/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts index 409a379b80..a53a188d6b 100644 --- a/apps/web/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts @@ -47,6 +47,9 @@ test.describe("Encryption tab", () => { await util.verifyDevice(recoveryKey); + // Prevent flakiness by scrolling to top of the tab + await page.getByRole("heading", { name: "Key storage" }).scrollIntoViewIfNeeded(); + await expect(content).toMatchScreenshot("default-tab.png", { mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")], }); diff --git a/apps/web/playwright/e2e/settings/general-room-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/general-room-settings-tab.spec.ts index 376412914a..e3f0963b59 100644 --- a/apps/web/playwright/e2e/settings/general-room-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/general-room-settings-tab.spec.ts @@ -20,7 +20,7 @@ test.describe("General room settings tab", () => { await app.viewRoomByName(roomName); }); - test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app }) => { + test("should be rendered properly", { tag: "@screenshot" }, async ({ page, app, axe }) => { const settings = await app.settings.openRoomSettings("General"); // Assert that "Show less" details element is rendered @@ -34,6 +34,9 @@ test.describe("General room settings tab", () => { // Assert that "Show more" details element is rendered instead of "Show more" await expect(settings.getByText("Show less")).not.toBeVisible(); await expect(settings.getByText("Show more")).toBeVisible(); + + axe.disableRules("color-contrast"); // XXX: We have some known contrast issues here + await expect(axe).toHaveNoViolations(); }); test("long address should not cause dialog to overflow", { tag: "@no-webkit" }, async ({ page, app, user }) => { diff --git a/apps/web/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/preferences-user-settings-tab.spec.ts index 8adbc74cc5..5414055534 100644 --- a/apps/web/playwright/e2e/settings/preferences-user-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/preferences-user-settings-tab.spec.ts @@ -25,7 +25,7 @@ test.describe("Preferences user settings tab", () => { labsFlags: ["feature_new_room_list"], }); - test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => { + test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => { await page.setViewportSize({ width: 1024, height: 4000 }); const tab = await app.settings.openUserSettings("Preferences"); // Assert that the top heading is rendered @@ -39,6 +39,8 @@ test.describe("Preferences user settings tab", () => { } `, }); + + await expect(axe).toHaveNoViolations(); }); test("should be able to change the app language", { tag: ["@no-firefox", "@no-webkit"] }, async ({ uut, user }) => { diff --git a/apps/web/playwright/e2e/settings/quick-settings-menu.spec.ts b/apps/web/playwright/e2e/settings/quick-settings-menu.spec.ts index e58d523c21..37d9adc458 100644 --- a/apps/web/playwright/e2e/settings/quick-settings-menu.spec.ts +++ b/apps/web/playwright/e2e/settings/quick-settings-menu.spec.ts @@ -8,11 +8,13 @@ Please see LICENSE files in the repository root for full details. import { test, expect } from "../../element-web-test"; test.describe("Quick settings menu", () => { - test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => { + test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => { await page.getByRole("button", { name: "Quick settings" }).click(); // Assert that the top heading is renderedc const settings = page.getByTestId("quick-settings-menu"); await expect(settings).toBeVisible(); await expect(settings).toMatchScreenshot("quick-settings.png"); + + await expect(axe).toHaveNoViolations(); }); }); diff --git a/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts index e575f71b72..c646e613c4 100644 --- a/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts @@ -25,7 +25,7 @@ test.describe("Roles & Permissions room settings tab", () => { settings = await app.settings.openRoomSettings("Roles & Permissions"); }); - test("should be able to change the role of a user", async ({ page, app, user }) => { + test("should be able to change the role of a user", async ({ page, app, user, axe }) => { const privilegedUserSection = settings.locator(".mx_SettingsFieldset").first(); const applyButton = privilegedUserSection.getByRole("button", { name: "Apply" }); @@ -55,5 +55,7 @@ test.describe("Roles & Permissions room settings tab", () => { settings = await app.settings.openRoomSettings("Roles & Permissions"); combobox = privilegedUserSection.getByRole("combobox", { name: user.userId }); await expect(combobox).toHaveValue("50"); + + await expect(axe).toHaveNoViolations(); }); }); diff --git a/apps/web/playwright/e2e/settings/security-user-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/security-user-settings-tab.spec.ts index 377cae7495..25f430c68d 100644 --- a/apps/web/playwright/e2e/settings/security-user-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/security-user-settings-tab.spec.ts @@ -32,12 +32,14 @@ test.describe("Security user settings tab", () => { }); test.describe("AnalyticsLearnMoreDialog", () => { - test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => { + test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user, axe }) => { 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( "Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1.png", ); + + await expect(axe).toHaveNoViolations(); }); }); diff --git a/apps/web/playwright/element-web-test.ts b/apps/web/playwright/element-web-test.ts index f7c6f5b8e2..cae16cce8d 100644 --- a/apps/web/playwright/element-web-test.ts +++ b/apps/web/playwright/element-web-test.ts @@ -24,7 +24,6 @@ import type { IConfigOptions } from "../src/IConfigOptions"; import { type Credentials } from "./plugins/homeserver"; import { ElementAppPage } from "./pages/ElementAppPage"; import { Crypto } from "./pages/crypto"; -import { Toasts } from "./pages/toasts"; import { Bot, type CreateBotOpts } from "./pages/bot"; import { Webserver } from "./plugins/webserver"; import { type WorkerOptions, type Services, test as base } from "./services"; @@ -52,7 +51,6 @@ export interface TestFixtures extends BaseTestFixtures { crypto: Crypto; room?: { roomId: string }; - toasts: Toasts; uut?: Locator; // Unit Under Test, useful place to refer a prepared locator botCreateOpts: CreateBotOpts; bot: Bot; @@ -92,9 +90,6 @@ export const test = base.extend({ crypto: async ({ page, homeserver, request }, use) => { await use(new Crypto(page, homeserver, request)); }, - toasts: async ({ page }, use) => { - await use(new Toasts(page)); - }, botCreateOpts: {}, bot: async ({ page, homeserver, botCreateOpts, user }, use, testInfo) => { diff --git a/apps/web/playwright/pages/network.ts b/apps/web/playwright/pages/network.ts index 3296fc8779..c007834113 100644 --- a/apps/web/playwright/pages/network.ts +++ b/apps/web/playwright/pages/network.ts @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import type { Page, Request, Route } from "@playwright/test"; +import type { Page, Request, Route, Disposable } from "@playwright/test"; import type { Client } from "./client"; /** @@ -16,7 +16,7 @@ import type { Client } from "./client"; */ export class Network { private isOffline = false; - private setupPromise?: Promise; + private setupPromise?: Promise; constructor( private page: Page, diff --git a/apps/web/playwright/pages/toasts.ts b/apps/web/playwright/pages/toasts.ts deleted file mode 100644 index 80ee3c9f26..0000000000 --- a/apps/web/playwright/pages/toasts.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import { type Page, expect, type Locator } from "@playwright/test"; - -export class Toasts { - public constructor(private readonly page: Page) {} - - /** - * Assert that a toast with the given title exists, and return it - * - * @param expectedTitle - Expected title of the toast - * @param timeout Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. - * @returns the Locator for the matching toast - */ - public async getToast(expectedTitle: string, timeout?: number): Promise { - const toast = this.page.locator(".mx_Toast_toast", { hasText: expectedTitle }).first(); - await expect(toast).toBeVisible({ timeout }); - return toast; - } - - /** - * Assert that no toasts exist - */ - public async assertNoToasts(): Promise { - await expect(this.page.locator(".mx_Toast_toast")).not.toBeVisible(); - } - - /** - * Accept a toast with the given title, only works for the first toast in the stack - * - * @param expectedTitle - Expected title of the toast - */ - public async acceptToast(expectedTitle: string): Promise { - const toast = await this.getToast(expectedTitle); - await toast.locator('.mx_Toast_buttons button[data-kind="primary"]').click(); - } - - /** - * Reject a toast with the given title, only works for the first toast in the stack - * - * @param expectedTitle - Expected title of the toast - */ - public async rejectToast(expectedTitle: string): Promise { - const toast = await this.getToast(expectedTitle); - await toast.locator('.mx_Toast_buttons button[data-kind="secondary"]').click(); - } -} diff --git a/apps/web/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png b/apps/web/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png index ffe31b9b26..738c7db132 100644 Binary files a/apps/web/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png and b/apps/web/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png differ diff --git a/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-linux.png b/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-linux.png index 93889d05e3..4adb4ad665 100644 Binary files a/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-linux.png and b/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-linux.png differ diff --git a/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-small-linux.png b/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-small-linux.png index ffbbf10025..56824baa75 100644 Binary files a/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-small-linux.png and b/apps/web/playwright/snapshots/composer/CIDER.spec.ts/emoji-picker-small-linux.png differ diff --git a/apps/web/playwright/snapshots/crypto/complete-security.spec.ts/complete-security-linux.png b/apps/web/playwright/snapshots/crypto/complete-security.spec.ts/complete-security-linux.png index dcdd2cb0f7..904e23c35a 100644 Binary files a/apps/web/playwright/snapshots/crypto/complete-security.spec.ts/complete-security-linux.png and b/apps/web/playwright/snapshots/crypto/complete-security.spec.ts/complete-security-linux.png differ diff --git a/apps/web/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png b/apps/web/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png index 3be7831755..0a99a4cf86 100644 Binary files a/apps/web/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png and b/apps/web/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png differ diff --git a/apps/web/playwright/snapshots/crypto/device-verification.spec.ts/recovery-key-linux.png b/apps/web/playwright/snapshots/crypto/device-verification.spec.ts/recovery-key-linux.png index 7683872ec8..f2e5ab1350 100644 Binary files a/apps/web/playwright/snapshots/crypto/device-verification.spec.ts/recovery-key-linux.png and b/apps/web/playwright/snapshots/crypto/device-verification.spec.ts/recovery-key-linux.png differ diff --git a/apps/web/playwright/snapshots/crypto/history-sharing.spec.ts/shared-history-invite-accepted-linux.png b/apps/web/playwright/snapshots/crypto/history-sharing.spec.ts/shared-history-invite-accepted-linux.png index ffae9b7929..adea2eac4e 100644 Binary files a/apps/web/playwright/snapshots/crypto/history-sharing.spec.ts/shared-history-invite-accepted-linux.png and b/apps/web/playwright/snapshots/crypto/history-sharing.spec.ts/shared-history-invite-accepted-linux.png differ diff --git a/apps/web/playwright/snapshots/crypto/toasts.spec.ts/verify-this-device-linux.png b/apps/web/playwright/snapshots/crypto/toasts.spec.ts/verify-this-device-linux.png index 7aeac7a9a9..01ad3384c1 100644 Binary files a/apps/web/playwright/snapshots/crypto/toasts.spec.ts/verify-this-device-linux.png and b/apps/web/playwright/snapshots/crypto/toasts.spec.ts/verify-this-device-linux.png differ diff --git a/apps/web/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png b/apps/web/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png index c84c746547..ba0d783961 100644 Binary files a/apps/web/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png and b/apps/web/playwright/snapshots/devtools/devtools.spec.ts/devtools-dialog-linux.png differ diff --git a/apps/web/playwright/snapshots/invite/invite-dialog.spec.ts/send-your-first-message-view-linux.png b/apps/web/playwright/snapshots/invite/invite-dialog.spec.ts/send-your-first-message-view-linux.png index 8408700761..9b14c9e11b 100644 Binary files a/apps/web/playwright/snapshots/invite/invite-dialog.spec.ts/send-your-first-message-view-linux.png and b/apps/web/playwright/snapshots/invite/invite-dialog.spec.ts/send-your-first-message-view-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png index 4ae6fff8ba..531802ed1c 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png index 9a9270e7db..3c63cdd84b 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/filter-menu-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/filter-menu-linux.png deleted file mode 100644 index fe056af3d3..0000000000 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/filter-menu-linux.png and /dev/null differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png index dd26980a18..ef53f1d6fb 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-sections.spec.ts/room-list-sections-collapsed-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-sections.spec.ts/room-list-sections-collapsed-linux.png new file mode 100644 index 0000000000..6ebcef8532 Binary files /dev/null and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-sections.spec.ts/room-list-sections-collapsed-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-sections.spec.ts/room-list-sections-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-sections.spec.ts/room-list-sections-linux.png new file mode 100644 index 0000000000..bd61342d60 Binary files /dev/null and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-sections.spec.ts/room-list-sections-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index 35a3cda353..d651340ace 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index 6050cc8298..489c080f55 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png index 41df0a9106..9e5528b3e5 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ diff --git a/apps/web/playwright/snapshots/oidc/oidc-native.spec.ts/token-expired-linux.png b/apps/web/playwright/snapshots/oidc/oidc-native.spec.ts/token-expired-linux.png deleted file mode 100644 index e85b83f2f1..0000000000 Binary files a/apps/web/playwright/snapshots/oidc/oidc-native.spec.ts/token-expired-linux.png and /dev/null differ diff --git a/apps/web/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png b/apps/web/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png index 406cddffde..8017745765 100644 Binary files a/apps/web/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png and b/apps/web/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png differ diff --git a/apps/web/playwright/snapshots/register/register.spec.ts/registration-linux.png b/apps/web/playwright/snapshots/register/register.spec.ts/registration-linux.png index 2babcd2343..ef0b3cc10a 100644 Binary files a/apps/web/playwright/snapshots/register/register.spec.ts/registration-linux.png and b/apps/web/playwright/snapshots/register/register.spec.ts/registration-linux.png differ diff --git a/apps/web/playwright/snapshots/register/register.spec.ts/server-picker-linux.png b/apps/web/playwright/snapshots/register/register.spec.ts/server-picker-linux.png index 65bf54fc88..b6c65b618d 100644 Binary files a/apps/web/playwright/snapshots/register/register.spec.ts/server-picker-linux.png and b/apps/web/playwright/snapshots/register/register.spec.ts/server-picker-linux.png differ diff --git a/apps/web/playwright/snapshots/register/register.spec.ts/terms-prompt-linux.png b/apps/web/playwright/snapshots/register/register.spec.ts/terms-prompt-linux.png index 9ce739c75f..6c0ed495c7 100644 Binary files a/apps/web/playwright/snapshots/register/register.spec.ts/terms-prompt-linux.png and b/apps/web/playwright/snapshots/register/register.spec.ts/terms-prompt-linux.png differ diff --git a/apps/web/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png b/apps/web/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png index ada2b8a60e..7fb7e011bb 100644 Binary files a/apps/web/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png and b/apps/web/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png b/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png index 65da0a4847..8cd0cbf863 100644 Binary files a/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png and b/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png b/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png index da4d594e23..14fd316f79 100644 Binary files a/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png and b/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png index dddecbe5ef..9a3b6e0054 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png index fe61d9dcb6..3861298d82 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png index 8caf08f4a5..51d8bc87a8 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png index 743dd8cac3..efdd7e3cba 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png index 001442fb3e..1a2a0746b9 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png index 0bb5fad763..25616a9437 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png index a42cb394d7..a6b0afc57d 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png index a9f601e822..2fa939ff85 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png index 27adfc2257..161a73f659 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png index be1a42d63f..f340588e3e 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png index 9108514219..5700565252 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png index 87d1cc85a4..bd69b42ab1 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png index c5bf0a7153..5f406151a8 100644 Binary files a/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png and b/apps/web/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png b/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png index 4f5ed98c6e..48625def3a 100644 Binary files a/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png and b/apps/web/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png index 5bd96ef009..8487b5a381 100644 Binary files a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png and b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-2-tab.spec.ts/standard-notifications-2-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png index 9452120bfb..21198098a4 100644 Binary files a/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png and b/apps/web/playwright/snapshots/settings/notifications/notifications-settings-tab.spec.ts/standard-notification-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png index 2a998720e9..ee058b786a 100644 Binary files a/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png and b/apps/web/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/quick-settings-menu.spec.ts/quick-settings-linux.png b/apps/web/playwright/snapshots/settings/quick-settings-menu.spec.ts/quick-settings-linux.png index bd95b29bfa..631bf77b30 100644 Binary files a/apps/web/playwright/snapshots/settings/quick-settings-menu.spec.ts/quick-settings-linux.png and b/apps/web/playwright/snapshots/settings/quick-settings-menu.spec.ts/quick-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png b/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png index 5596a89510..95657048ac 100644 Binary files a/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png and b/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png b/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png index 284902212d..f5f3fa0382 100644 Binary files a/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png and b/apps/web/playwright/snapshots/settings/room-settings/room-security-tab.spec.ts/room-security-settings-world-readable-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png b/apps/web/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png index aca5393f02..7a64332f1b 100644 Binary files a/apps/web/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png and b/apps/web/playwright/snapshots/settings/room-settings/room-video-tab.spec.ts/room-video-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png b/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png index 5ae88a2b1c..5248968436 100644 Binary files a/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png and b/apps/web/playwright/snapshots/settings/security-user-settings-tab.spec.ts/security-settings-tab-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-create-menu-linux.png b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-create-menu-linux.png index 8e20ad8833..17d84a16a1 100644 Binary files a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-create-menu-linux.png and b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-create-menu-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png index 1200cfd122..a1d376d62f 100644 Binary files a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png and b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-visibility-settings-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-joined-the-room-linux.png b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-joined-the-room-linux.png index 44be83cb88..8f3f0998ed 100644 Binary files a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-joined-the-room-linux.png and b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-joined-the-room-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-was-banned-linux.png b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-was-banned-linux.png index 981989e501..d6aa806afa 100644 Binary files a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-was-banned-linux.png and b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/bot-was-banned-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-ban-messages-linux.png b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-ban-messages-linux.png index d8163d1056..e854c65166 100644 Binary files a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-ban-messages-linux.png and b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-ban-messages-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-leave-messages-linux.png b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-leave-messages-linux.png index c011103b78..7b06c2cdb3 100644 Binary files a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-leave-messages-linux.png and b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-join-leave-messages-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-ban-messages-linux.png b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-ban-messages-linux.png index b892fb31b2..3e00f1f1f3 100644 Binary files a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-ban-messages-linux.png and b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-ban-messages-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-join-leave-messages-linux.png b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-join-leave-messages-linux.png index abb228f9d0..b8c2499868 100644 Binary files a/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-join-leave-messages-linux.png and b/apps/web/playwright/snapshots/timeline/event-list-summary.spec.ts/multiple-people-join-leave-messages-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png index 781d4c67fe..4184b312b3 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png index 6b849468b8..2bb0c86268 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png index 64ad63d865..7bb4da1f76 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png index 475be8c267..af2e2dc1f6 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png index f4af327000..7907870775 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png index b142d6b940..669c1bc2f2 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png index 2e7a681247..31af6f9c6f 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png index fe75aaabd3..90e669dd8d 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png index c75b214a5e..3fe3760f94 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png index 7ce4b411db..65e6bc082b 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png index a71eca7b90..461e0b94b4 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png index 475be8c267..af2e2dc1f6 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png index 8f453b8de2..2db0d572c2 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png index 37670d5754..2436e205c6 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png index b22eb6d474..33903a62db 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png index ad00531521..c37018b8b5 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/highlighted-search-results-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/highlighted-search-results-linux.png index 1ca92215fa..ac73e3947c 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/highlighted-search-results-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/highlighted-search-results-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png index 7924725ead..9fe134e7f6 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png index e59c59796b..04e0a88ffa 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png index cfd7db0fdb..311df4e505 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/search-results-with-TextualEvent-linux.png b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/search-results-with-TextualEvent-linux.png index 27746d1981..43de3eb371 100644 Binary files a/apps/web/playwright/snapshots/timeline/timeline.spec.ts/search-results-with-TextualEvent-linux.png and b/apps/web/playwright/snapshots/timeline/timeline.spec.ts/search-results-with-TextualEvent-linux.png differ diff --git a/apps/web/playwright/testcontainers/mas.ts b/apps/web/playwright/testcontainers/mas.ts index 45752fc10e..850f22f68d 100644 --- a/apps/web/playwright/testcontainers/mas.ts +++ b/apps/web/playwright/testcontainers/mas.ts @@ -11,7 +11,7 @@ import { } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/matrix-authentication-service:main@sha256:f54c2214354ec3294694a525523debb5f38c8580c1a5afc8cdec0f8372374ef3"; + "ghcr.io/element-hq/matrix-authentication-service:main@sha256:51b79f0bb1914b412bd87b5f0817820d140af84c08c22d7816ba675ee0c67150"; /** * MatrixAuthenticationServiceContainer which freezes the docker digest to diff --git a/apps/web/playwright/testcontainers/synapse.ts b/apps/web/playwright/testcontainers/synapse.ts index f67d51659a..20a678c2d7 100644 --- a/apps/web/playwright/testcontainers/synapse.ts +++ b/apps/web/playwright/testcontainers/synapse.ts @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/synapse:develop@sha256:73fe964d854412cd905f4f0e668b2a9d10edc86891036e03656714de0311f6f6"; + "ghcr.io/element-hq/synapse:develop@sha256:46e602b7b26a962eb59e9af07f4e0ebf2c09639475b96c81252ca2f39d32cd8c"; /** * SynapseContainer which freezes the docker digest to stabilise tests, diff --git a/apps/web/project.json b/apps/web/project.json index 95f9a6828d..59fa9f3a66 100644 --- a/apps/web/project.json +++ b/apps/web/project.json @@ -44,7 +44,7 @@ "parallel": false, "cwd": "apps/web" }, - "dependsOn": ["^build"] + "dependsOn": ["^build", "^build:playwright"] }, "test:unit": { "executor": "@nx/jest:jest", @@ -53,6 +53,16 @@ "cwd": "apps/web" }, "dependsOn": ["^build"] + }, + "test:playwright": { + "command": "playwright test", + "options": { "cwd": "apps/web" }, + "dependsOn": ["^build:playwright"] + }, + "test:playwright:screenshots": { + "command": "playwright-screenshots nx test:playwright --update-snapshots --project=Chrome --grep @screenshot", + "options": { "cwd": "apps/web" }, + "dependsOn": ["^build:playwright"] } } } diff --git a/apps/web/res/css/_common.pcss b/apps/web/res/css/_common.pcss index 355d480b73..4cadbe71c6 100644 --- a/apps/web/res/css/_common.pcss +++ b/apps/web/res/css/_common.pcss @@ -163,14 +163,6 @@ b { font-weight: bold; } -h2 { - color: $primary-content; - font: var(--cpd-font-heading-lg-regular); - letter-spacing: var(--cpd-font-letter-spacing-heading-lg); - margin-top: 16px; - margin-bottom: 16px; -} - a:hover, a:link, a:visited { diff --git a/apps/web/res/css/_components.pcss b/apps/web/res/css/_components.pcss index 9435e53b6f..fdf774a754 100644 --- a/apps/web/res/css/_components.pcss +++ b/apps/web/res/css/_components.pcss @@ -68,8 +68,8 @@ @import "./structures/_LeftPanel.pcss"; @import "./structures/_MainSplit.pcss"; @import "./structures/_MatrixChat.pcss"; -@import "./structures/_MessagePanel.pcss"; @import "./structures/_NonUrgentToastContainer.pcss"; +@import "./structures/_PictureInPictureDragger.pcss"; @import "./structures/_QuickSettingsButton.pcss"; @import "./structures/_RightPanel.pcss"; @import "./structures/_RoomSearch.pcss"; @@ -232,13 +232,13 @@ @import "./views/messages/_MPollBody.pcss"; @import "./views/messages/_MStickerBody.pcss"; @import "./views/messages/_MTextBody.pcss"; -@import "./views/messages/_MVideoBody.pcss"; @import "./views/messages/_MediaBody.pcss"; @import "./views/messages/_MessageActionBar.pcss"; @import "./views/messages/_MjolnirBody.pcss"; @import "./views/messages/_ReactionsRow.pcss"; @import "./views/messages/_RoomAvatarEvent.pcss"; @import "./views/messages/_TextualEvent.pcss"; +@import "./views/messages/_ThreadActionBar.pcss"; @import "./views/messages/_UnknownBody.pcss"; @import "./views/messages/_ViewSourceEvent.pcss"; @import "./views/messages/_common_CryptoEvent.pcss"; @@ -335,6 +335,7 @@ @import "./views/settings/_SpellCheckLanguages.pcss"; @import "./views/settings/_ThemeChoicePanel.pcss"; @import "./views/settings/_UpdateCheckButton.pcss"; +@import "./views/settings/_UserPersonalInfoSettings.pcss"; @import "./views/settings/_UserProfileSettings.pcss"; @import "./views/settings/encryption/_AdvancedPanel.pcss"; @import "./views/settings/encryption/_ChangeRecoveryKey.pcss"; @@ -374,7 +375,6 @@ @import "./views/voip/_DialPad.pcss"; @import "./views/voip/_DialPadContextMenu.pcss"; @import "./views/voip/_DialPadModal.pcss"; -@import "./views/voip/_LegacyCallPreview.pcss"; @import "./views/voip/_LegacyCallView.pcss"; @import "./views/voip/_LegacyCallViewForRoom.pcss"; @import "./views/voip/_LegacyCallViewHeader.pcss"; diff --git a/apps/web/res/css/structures/_MessagePanel.pcss b/apps/web/res/css/structures/_MessagePanel.pcss deleted file mode 100644 index fb2830bce7..0000000000 --- a/apps/web/res/css/structures/_MessagePanel.pcss +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_MessagePanel_myReadMarker { - height: 0; - margin: 0; - padding: 0; - border: 0; - - hr { - border-top: solid 1px $accent; - border-bottom: solid 1px $accent; - margin-top: 0; - position: relative; - top: -1px; - z-index: 1; - will-change: width; - transition: - width 400ms easeinsine 1s, - opacity 400ms easeinsine 1s; - width: 99%; - opacity: 1; - } -} diff --git a/apps/web/res/css/structures/_PictureInPictureDragger.pcss b/apps/web/res/css/structures/_PictureInPictureDragger.pcss new file mode 100644 index 0000000000..d6effd3b20 --- /dev/null +++ b/apps/web/res/css/structures/_PictureInPictureDragger.pcss @@ -0,0 +1,20 @@ +/* +Copyright 2026 Element Creations 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_PictureInPictureDragger { + cursor: grab; + user-select: none; + left: 0; + position: fixed; + top: 0; + /* Display above any widget elements */ + z-index: 102; +} + +.mx_PictureInPictureDragger:active { + cursor: grabbing; +} diff --git a/apps/web/res/css/views/dialogs/_DevtoolsDialog.pcss b/apps/web/res/css/views/dialogs/_DevtoolsDialog.pcss index b691863d86..f4c4dbcdcb 100644 --- a/apps/web/res/css/views/dialogs/_DevtoolsDialog.pcss +++ b/apps/web/res/css/views/dialogs/_DevtoolsDialog.pcss @@ -30,8 +30,7 @@ Please see LICENSE files in the repository root for full details. .mx_DevTools_toolHeading { color: var(--cpd-color-text-secondary); - font-weight: var(--cpd-font-weight-semibold); - font-size: var(--cpd-font-size-heading-sm); + font: var(--cpd-font-heading-sm-semibold); } .mx_DevTools_content { diff --git a/apps/web/res/css/views/dialogs/_MessageEditHistoryDialog.pcss b/apps/web/res/css/views/dialogs/_MessageEditHistoryDialog.pcss index 5d06545ae8..d14bb3ca60 100644 --- a/apps/web/res/css/views/dialogs/_MessageEditHistoryDialog.pcss +++ b/apps/web/res/css/views/dialogs/_MessageEditHistoryDialog.pcss @@ -82,13 +82,11 @@ Please see LICENSE files in the repository root for full details. } } - .mx_MessageActionBar .mx_AccessibleButton { - display: flex; - align-items: center; + .mx_HistoryActionBar { + border-radius: 0 !important; + } - padding-inline-start: $spacing-8; - padding-inline-end: $spacing-8; - - font-size: $font-15px; + .mx_HistoryActionBar [data-presentation="label"] { + line-height: 24px !important; } } diff --git a/apps/web/res/css/views/emojipicker/_EmojiPicker.pcss b/apps/web/res/css/views/emojipicker/_EmojiPicker.pcss index 60a364a6e3..b24fe806d9 100644 --- a/apps/web/res/css/views/emojipicker/_EmojiPicker.pcss +++ b/apps/web/res/css/views/emojipicker/_EmojiPicker.pcss @@ -118,6 +118,7 @@ Please see LICENSE files in the repository root for full details. .mx_EmojiPicker_category_label { width: 304px; + font: var(--cpd-font-heading-sm-semibold); } .mx_EmojiPicker_list { diff --git a/apps/web/res/css/views/messages/_MVideoBody.pcss b/apps/web/res/css/views/messages/_MVideoBody.pcss deleted file mode 100644 index 0727a8dc44..0000000000 --- a/apps/web/res/css/views/messages/_MVideoBody.pcss +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020, 2021 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -span.mx_MVideoBody { - overflow: hidden; - - .mx_MVideoBody_container { - border-radius: var(--MBody-border-radius); - overflow: hidden; - - video { - height: 100%; - width: 100%; - } - } -} diff --git a/apps/web/res/css/views/messages/_MessageActionBar.pcss b/apps/web/res/css/views/messages/_MessageActionBar.pcss index 31a83d3feb..d078632ce2 100644 --- a/apps/web/res/css/views/messages/_MessageActionBar.pcss +++ b/apps/web/res/css/views/messages/_MessageActionBar.pcss @@ -9,19 +9,8 @@ Please see LICENSE files in the repository root for full details. .mx_MessageActionBar { --MessageActionBar-size-button: 28px; --MessageActionBar-size-margin: 3px; - --MessageActionBar-item-hover-background: var(--cpd-color-bg-subtle-secondary); - --MessageActionBar-item-hover-borderRadius: 6px; - --MessageActionBar-item-hover-zIndex: 1; position: absolute; - visibility: hidden; - cursor: pointer; - display: flex; - gap: var(--cpd-space-0-5x); - line-height: $font-24px; - border-radius: 8px; - background: $background; - border: var(--cpd-border-width-1) solid var(--cpd-color-border-disabled); top: calc( -1 * ( @@ -75,51 +64,4 @@ Please see LICENSE files in the repository root for full details. left: 0; } } - - > * { - white-space: nowrap; - display: inline-block; - position: relative; - margin: var(--MessageActionBar-size-margin); - - &:hover { - background: var(--MessageActionBar-item-hover-background); - border-radius: var(--MessageActionBar-item-hover-borderRadius); - z-index: var(--MessageActionBar-item-hover-zIndex); - } - } - - .mx_MessageActionBar_iconButton { - --MessageActionBar-icon-size: 20px; - width: var(--MessageActionBar-size-button); - height: var(--MessageActionBar-size-button); - color: var(--cpd-color-icon-secondary); - display: flex; - align-items: center; - justify-content: center; - - svg { - height: var(--MessageActionBar-icon-size); - width: var(--MessageActionBar-icon-size); - flex: 0 0 var(--MessageActionBar-icon-size); - } - - &:disabled, - &[disabled] { - cursor: not-allowed; - opacity: 0.75; - } - - &:hover { - color: var(--cpd-color-icon-primary); - } - - &.mx_MessageActionBar_downloadButton { - &.mx_MessageActionBar_downloadSpinnerButton { - svg { - display: none; /* hide the download icon */ - } - } - } - } } diff --git a/apps/web/res/css/views/messages/_ThreadActionBar.pcss b/apps/web/res/css/views/messages/_ThreadActionBar.pcss new file mode 100644 index 0000000000..e6f71fc024 --- /dev/null +++ b/apps/web/res/css/views/messages/_ThreadActionBar.pcss @@ -0,0 +1,58 @@ +/* + * Copyright 2026 Element Creations 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_ThreadActionBar { + position: absolute; + visibility: hidden; + top: calc(-1 * (28px + 2 * (3px + var(--cpd-border-width-1)))); + right: 8px; + user-select: none; + /* Ensure the action bar appears above other things like the read marker */ + /* and sender avatar (for small screens) */ + z-index: 10; + + /* Adds a previous event safe area so that you can't accidentally hover the */ + /* previous event while trying to mouse into the action bar or from the */ + /* react button to its tooltip. */ + &::before { + content: ""; + position: absolute; + /* tooltip safe mousing area + tooltip overhang + */ + /* action bar + action bar offset from event */ + width: calc(10px + 48px + 100% + 8px); + /* safe area + action bar */ + height: calc(20px + 100%); + top: -12px; + left: -58px; + z-index: -1; + cursor: initial; + + /* stylelint-disable-next-line max-line-length */ + .mx_GenericEventListSummary[data-layout="bubble"] + .mx_GenericEventListSummary_toggle + ~ .mx_GenericEventListSummary_unstyledList + .mx_EventTile_info:first-of-type + & { + /* improve clickability of "collapse" link button on bubble layout by reducing width and height values */ + /* mx_GenericEventListSummary_toggle ~: to apply rules to action bar when "collapse" button is available */ + /* mx_EventTile_info:first-of-type: to apply rules to the info event tile just under "collapse" button */ + /* TODO: use a new class name instead */ + width: 100%; + height: 100%; + top: 0; + left: 0; + } + + .mx_EventTile_info .mx_ViewSourceEvent ~ & { + /* improve clickability of view source event toggle button by removing vertical safe area */ + width: 100%; + height: 100%; + top: 0; + left: 0; + } + } +} diff --git a/apps/web/res/css/views/rooms/_EventTile.pcss b/apps/web/res/css/views/rooms/_EventTile.pcss index 59bb2d23d0..63d1bdaee2 100644 --- a/apps/web/res/css/views/rooms/_EventTile.pcss +++ b/apps/web/res/css/views/rooms/_EventTile.pcss @@ -938,10 +938,10 @@ $left-gutter: 64px; } } -.mx_EventTile:hover .mx_MessageActionBar, -.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar, -[data-whatinput="keyboard"] .mx_EventTile:focus-within .mx_MessageActionBar, -.mx_EventTile:focus-visible:focus-within .mx_MessageActionBar { +.mx_EventTile:hover .mx_ThreadActionBar, +.mx_EventTile.mx_EventTile_actionBarFocused .mx_ThreadActionBar, +[data-whatinput="keyboard"] .mx_EventTile:focus-within .mx_ThreadActionBar, +.mx_EventTile:focus-visible:focus-within .mx_ThreadActionBar { visibility: visible; } @@ -1378,6 +1378,10 @@ $left-gutter: 64px; display: flex; } +.mx_EventTile_annotatedInline { + display: inline-flex; +} + .mx_EventTile_footer { display: flex; gap: var(--cpd-space-2x); diff --git a/apps/web/res/css/views/rooms/_NewRoomIntro.pcss b/apps/web/res/css/views/rooms/_NewRoomIntro.pcss index bdd6244f42..722b17b6a5 100644 --- a/apps/web/res/css/views/rooms/_NewRoomIntro.pcss +++ b/apps/web/res/css/views/rooms/_NewRoomIntro.pcss @@ -37,9 +37,8 @@ Please see LICENSE files in the repository root for full details. } > h2 { - margin-top: 24px; - font-size: $font-24px; - font-weight: var(--cpd-font-weight-semibold); + margin: var(--cpd-space-6x) 0 var(--cpd-space-4x); + font: var(--cpd-font-heading-md-semibold); } > p { diff --git a/apps/web/res/css/views/settings/_UserPersonalInfoSettings.pcss b/apps/web/res/css/views/settings/_UserPersonalInfoSettings.pcss new file mode 100644 index 0000000000..686f0aaa3e --- /dev/null +++ b/apps/web/res/css/views/settings/_UserPersonalInfoSettings.pcss @@ -0,0 +1,12 @@ +/* +Copyright 2026 Element Creations 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_UserPersonalInfoSettings { + h2 { + margin: var(--cpd-space-4x) 0; + } +} diff --git a/apps/web/res/css/views/settings/_UserProfileSettings.pcss b/apps/web/res/css/views/settings/_UserProfileSettings.pcss index 9f05851e29..845aa52c1c 100644 --- a/apps/web/res/css/views/settings/_UserProfileSettings.pcss +++ b/apps/web/res/css/views/settings/_UserProfileSettings.pcss @@ -9,6 +9,10 @@ Please see LICENSE files in the repository root for full details. .mx_UserProfileSettings { border-bottom: 1px solid $quinary-content; + h2 { + margin: var(--cpd-space-4x) 0; + } + .mx_UserProfileSettings_profile { display: flex; margin-top: var(--cpd-space-6x); diff --git a/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss b/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss index ce3c9266c3..c98732e884 100644 --- a/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss +++ b/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss @@ -36,5 +36,5 @@ Please see LICENSE files in the repository root for full details. grid-template-columns: minmax(0, 1fr); gap: $spacing-32; - padding: $spacing-16 0; + margin: $spacing-16 0; } diff --git a/apps/web/res/css/views/spaces/_SpaceCreateMenu.pcss b/apps/web/res/css/views/spaces/_SpaceCreateMenu.pcss index 9b85b30d91..6c6d50fffc 100644 --- a/apps/web/res/css/views/spaces/_SpaceCreateMenu.pcss +++ b/apps/web/res/css/views/spaces/_SpaceCreateMenu.pcss @@ -23,7 +23,7 @@ Please see LICENSE files in the repository root for full details. > div { > h2 { - font-weight: var(--cpd-font-weight-semibold); + font: var(--cpd-font-heading-sm-semibold); font-size: $font-18px; margin-top: 4px; } diff --git a/apps/web/res/css/views/typography/_Heading.pcss b/apps/web/res/css/views/typography/_Heading.pcss index a6204e869d..7d190192fc 100644 --- a/apps/web/res/css/views/typography/_Heading.pcss +++ b/apps/web/res/css/views/typography/_Heading.pcss @@ -12,6 +12,7 @@ Please see LICENSE files in the repository root for full details. .mx_Heading_h4 { margin-inline: unset; margin-block: unset; + color: $primary-content; } .mx_Heading_h1 { diff --git a/apps/web/res/css/views/voip/_LegacyCallPreview.pcss b/apps/web/res/css/views/voip/_LegacyCallPreview.pcss deleted file mode 100644 index 3a8bf5af9f..0000000000 --- a/apps/web/res/css/views/voip/_LegacyCallPreview.pcss +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2021 Šimon Brandner - -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_LegacyCallPreview { - align-items: flex-end; - display: flex; - flex-direction: column; - gap: $spacing-16; - left: 0; - position: fixed; - top: 0; - /* Display above any widget elements */ - z-index: 102; - - .mx_VideoFeed_remote.mx_VideoFeed_voice { - min-height: 150px; - } - - .mx_VideoFeed_local { - border-radius: 8px; - overflow: hidden; - } -} diff --git a/apps/web/src/Lifecycle.ts b/apps/web/src/Lifecycle.ts index 78c6cc2de6..d7d139249a 100644 --- a/apps/web/src/Lifecycle.ts +++ b/apps/web/src/Lifecycle.ts @@ -18,7 +18,6 @@ import { decodeBase64, } from "matrix-js-sdk/src/matrix"; import { type AESEncryptedSecretStoragePayload } from "matrix-js-sdk/src/types"; -import { type QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; import { type IMatrixClientCreds, MatrixClientPeg, type MatrixClientPegAssignOpts } from "./MatrixClientPeg"; @@ -81,6 +80,7 @@ import { } from "./utils/tokens/tokens"; import { TokenRefresher } from "./utils/oidc/TokenRefresher"; import { checkBrowserSupport } from "./SupportedBrowser"; +import { type URLParams } from "./vector/url_utils.ts"; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; @@ -148,7 +148,7 @@ interface ILoadSessionOpts { guestIsUrl?: string; ignoreGuest?: boolean; defaultDeviceDisplayName?: string; - fragmentQueryParams?: QueryDict; + urlParams?: URLParams; abortSignal?: AbortSignal; } @@ -187,7 +187,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise let enableGuest = opts.enableGuest || false; const guestHsUrl = opts.guestHsUrl; const guestIsUrl = opts.guestIsUrl; - const fragmentQueryParams = opts.fragmentQueryParams || {}; + const urlParams = opts.urlParams; const defaultDeviceDisplayName = opts.defaultDeviceDisplayName; if (enableGuest && !guestHsUrl) { @@ -195,12 +195,12 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise enableGuest = false; } - if (enableGuest && guestHsUrl && fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_access_token) { + if (enableGuest && guestHsUrl && urlParams?.guest?.guest_user_id && urlParams?.guest?.guest_access_token) { logger.log("Using guest access credentials"); await doSetLoggedIn( { - userId: fragmentQueryParams.guest_user_id as string, - accessToken: fragmentQueryParams.guest_access_token as string, + userId: urlParams.guest.guest_user_id, + accessToken: urlParams.guest.guest_access_token, homeserverUrl: guestHsUrl, identityServerUrl: guestIsUrl, guest: true, @@ -264,38 +264,36 @@ export async function getStoredSessionOwner(): Promise<[string, boolean] | [null * If query string includes OIDC authorization code flow parameters attempt to login using oidc flow * Else, we may be returning from SSO - attempt token login * - * @param {Object} queryParams string->string map of the - * query-parameters extracted from the real query-string of the starting - * URI. + * @param urlParams the parameters read in at app load time from the url * - * @param {string} defaultDeviceDisplayName - * @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again" + * @param defaultDeviceDisplayName + * @param fragmentAfterLogin path to go to after a successful login, only used for "Try again" * - * @returns {Promise} promise which resolves to true if we completed the delegated auth login + * @returns promise which resolves to true if we completed the delegated auth login * else false */ export async function attemptDelegatedAuthLogin( - queryParams: QueryDict, + urlParams: URLParams, defaultDeviceDisplayName?: string, fragmentAfterLogin?: string, ): Promise { - if (queryParams.code && queryParams.state) { + if (urlParams.oidc) { console.log("We have OIDC params - attempting OIDC login"); - return attemptOidcNativeLogin(queryParams); + return attemptOidcNativeLogin(urlParams["oidc"]); } - return attemptTokenLogin(queryParams, defaultDeviceDisplayName, fragmentAfterLogin); + return attemptTokenLogin(urlParams["legacy_sso"], defaultDeviceDisplayName, fragmentAfterLogin); } /** * Attempt to login by completing OIDC authorization code flow - * @param queryParams string->string map of the query-parameters extracted from the real query-string of the starting URI. - * @returns Promise that resolves to true when login succceeded, else false + * @param urlParams subset of app-load url parameters relating to oidc auth + * @returns Promise that resolves to true when login succeeded, else false */ -async function attemptOidcNativeLogin(queryParams: QueryDict): Promise { +async function attemptOidcNativeLogin(urlParams: NonNullable): Promise { try { const { accessToken, refreshToken, homeserverUrl, identityServerUrl, idToken, clientId, issuer } = - await completeOidcLogin(queryParams); + await completeOidcLogin(urlParams); const { user_id: userId, @@ -354,22 +352,20 @@ async function getUserIdFromAccessToken( } /** - * @param {QueryDict} queryParams string->string map of the - * query-parameters extracted from the real query-string of the starting - * URI. + @param urlParams subset of app-load url parameters relating to legacy sso auth * - * @param {string} defaultDeviceDisplayName - * @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again" + * @param defaultDeviceDisplayName + * @param fragmentAfterLogin path to go to after a successful login, only used for "Try again" * - * @returns {Promise} promise which resolves to true if we completed the token + * @returns promise which resolves to true if we completed the token * login, else false */ export function attemptTokenLogin( - queryParams: QueryDict, + urlParams: URLParams["legacy_sso"], defaultDeviceDisplayName?: string, fragmentAfterLogin?: string, ): Promise { - if (!queryParams.loginToken) { + if (!urlParams?.loginToken) { return Promise.resolve(false); } @@ -384,7 +380,7 @@ export function attemptTokenLogin( } return sendLoginRequest(homeserver, identityServer, "m.login.token", { - token: queryParams.loginToken as string, + token: urlParams.loginToken, initial_device_display_name: defaultDeviceDisplayName, }) .then(async function (creds) { diff --git a/apps/web/src/Notifier.ts b/apps/web/src/Notifier.ts index dbe890909e..b0a2ce5b42 100644 --- a/apps/web/src/Notifier.ts +++ b/apps/web/src/Notifier.ts @@ -81,6 +81,59 @@ const msgTypeHandlers: Record string | null> = { }, }; +/** + * Extracts plain text from a message body, replacing any spoilered content + * with '[Spoiler]' to prevent spoilers in desktop notifications. + */ +function getNotificationBodyWithoutSpoilers(ev: MatrixEvent): string { + const content = ev.getContent(); + const plainBody = content.body ?? ""; + const formattedBody = content.formatted_body; + + if (typeof formattedBody !== "string" || !formattedBody.length) { + return plainBody; + } + + /** Recursively walks HTML tree to hide spoilers. */ + function replaceSpoilers(node: Node): Node { + if (node.nodeType !== Node.ELEMENT_NODE || !(node instanceof Element)) { + return node; + } + + if (node.hasAttribute("data-mx-spoiler")) { + const e = document.createElement("span"); + e.appendChild(document.createTextNode("[Spoiler]")); + return e; + } + + for (const childNode of node.childNodes) { + node.replaceChild(replaceSpoilers(childNode), childNode); + } + + return node; + } + + try { + // Dev note: ideally we would reuse more of the existing rendering stack + // rather than re-parsing and updating the generated HTML here. However, + // that rendering stack is currently quite consolidated and cannot + // easily be refactored to allow the call-site to control how spoilers + // are rendered. The problem is that we now need two different output + // formats: + // - The existing format where spoilers are wrapped in html tags + // - The new format where the spoilered text is replaced with [Spoiler] + + const parser = new DOMParser(); + const doc = parser.parseFromString(formattedBody, "text/html"); + + // Use textContent rather than innerHTML/outerHTML since textContent is + // XSS-safe and the input is untrusted. + return replaceSpoilers(doc.body).textContent ?? plainBody; + } catch { + return plainBody; + } +} + export const enum NotifierEvent { NotificationHiddenChange = "notification_hidden_change", } @@ -134,7 +187,7 @@ class NotifierClass extends TypedEventEmitter { - const inviter = new MultiInviter(client, roomId, options); - return { states: await inviter.invite(addresses), inviter }; -} - export function showStartChatInviteDialog(initialText = ""): void { // This dialog handles the room creation internally - we don't need to worry about it. Modal.createDialog( diff --git a/apps/web/src/audio/VoiceRecording.ts b/apps/web/src/audio/VoiceRecording.ts index 705b96375a..44a016b995 100644 --- a/apps/web/src/audio/VoiceRecording.ts +++ b/apps/web/src/audio/VoiceRecording.ts @@ -103,10 +103,14 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { private async makeRecorder(): Promise { try { + const requestedDeviceId = MediaDeviceHandler.getAudioInput(); + const deviceIdConstraint = + requestedDeviceId && requestedDeviceId !== "default" ? { deviceId: { exact: requestedDeviceId } } : {}; + this.recorderStream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: CHANNELS, - deviceId: MediaDeviceHandler.getAudioInput(), + ...deviceIdConstraint, autoGainControl: { ideal: MediaDeviceHandler.getAudioAutoGainControl() }, echoCancellation: { ideal: MediaDeviceHandler.getAudioEchoCancellation() }, noiseSuppression: { ideal: MediaDeviceHandler.getAudioNoiseSuppression() }, diff --git a/apps/web/src/components/structures/ContextMenu.tsx b/apps/web/src/components/structures/ContextMenu.tsx index 9d4b76e38e..3cd43b5d91 100644 --- a/apps/web/src/components/structures/ContextMenu.tsx +++ b/apps/web/src/components/structures/ContextMenu.tsx @@ -8,7 +8,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type CSSProperties, type RefObject, type SyntheticEvent, useRef, useState } from "react"; +import React, { + type JSX, + type CSSProperties, + type RefObject, + type SyntheticEvent, + useRef, + useState, + type AriaRole, +} from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; import FocusLock from "react-focus-lock"; @@ -74,27 +82,31 @@ export interface MenuProps extends IPosition { export interface IProps extends MenuProps { // If true, insert an invisible screen-sized element behind the menu that when clicked will close it. - hasBackground?: boolean; + "hasBackground"?: boolean; // whether this context menu should be focus managed. If false it must handle itself - managed?: boolean; - wrapperClassName?: string; - menuClassName?: string; + "managed"?: boolean; + "wrapperClassName"?: string; + "menuClassName"?: string; // If true, this context menu will be mounted as a child to the parent container. Otherwise // it will be mounted to a container at the root of the DOM. - mountAsChild?: boolean; + "mountAsChild"?: boolean; // If specified, contents will be wrapped in a FocusLock, this is only needed if the context menu is being rendered // within an existing FocusLock e.g inside a modal. - focusLock?: boolean; + "focusLock"?: boolean; // call onFinished on any interaction with the menu - closeOnInteraction?: boolean; + "closeOnInteraction"?: boolean; // Function to be called on menu close onFinished(this: void): void; // on resize callback windowResize?(this: void): void; + + // Role & label for accessibility + "role"?: AriaRole; + "aria-label"?: string; } interface IState { @@ -257,9 +269,11 @@ export default class ContextMenu extends React.PureComponent {background} diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index ce5bfd4598..e93799fb1a 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -20,7 +20,6 @@ import { type SyncStateData, type TimelineEvents, } from "matrix-js-sdk/src/matrix"; -import { type QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; import { throttle } from "lodash"; import { CryptoEvent, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; @@ -141,6 +140,8 @@ import Markdown from "../../Markdown"; import { LinkedTextConfiguration, sanitizeHtmlParams } from "../../Linkify"; import { isOnlyAdmin } from "../../utils/membership"; import { ModuleApi } from "../../modules/Api.ts"; +import { type IScreen } from "../../vector/routing.ts"; +import { type URLParams } from "../../vector/url_utils.ts"; // legacy export export { default as Views } from "../../Views"; @@ -152,21 +153,14 @@ const AUTH_SCREENS = ["register", "mobile_register", "login", "forgot_password", // re-factoring to be included in this list in future. const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, Action.CreateChat, Action.CreateRoom]; -interface IScreen { - screen: string; - params?: QueryDict; -} - interface IProps { config: ConfigOptions; onNewScreen: (screen: string, replaceLast: boolean) => void; enableGuest?: boolean; - // the queryParams extracted from the [real] query-string of the URI - realQueryParams: QueryDict; - // the initial queryParams extracted from the hash-fragment of the URI - startingFragmentQueryParams?: QueryDict; + // the params extracted from the [real] query-string & fragment of the URI + urlParams: URLParams; // called when we have completed a token login - onTokenLoginCompleted: () => void; + onTokenLoginCompleted: (urlParams: URLParams, fragmentAfterLogin: string) => void; // Represents the screen to display as a result of parsing the initial window.location initialScreenAfterLogin?: IScreen; // displayname, if any, to set on the device when logging in/registering. @@ -227,11 +221,8 @@ interface IState { export default class MatrixChat extends React.PureComponent { public static displayName = "MatrixChat"; - public static defaultProps = { - realQueryParams: {}, - startingFragmentQueryParams: {}, + public static defaultProps: Partial = { config: {}, - onTokenLoginCompleted: (): void => {}, }; private firstSyncComplete = false; @@ -353,18 +344,14 @@ export default class MatrixChat extends React.PureComponent { // Otherwise, the first thing to do is to try the token params in the query-string const delegatedAuthSucceeded = await Lifecycle.attemptDelegatedAuthLogin( - this.props.realQueryParams, + this.props.urlParams, this.props.defaultDeviceDisplayName, this.getFragmentAfterLogin(), ); // remove the loginToken or auth code from the URL regardless - if ( - this.props.realQueryParams?.loginToken || - this.props.realQueryParams?.code || - this.props.realQueryParams?.state - ) { - this.props.onTokenLoginCompleted(); + if (!!this.props.urlParams.legacy_sso || !!this.props.urlParams.oidc) { + this.props.onTokenLoginCompleted(this.props.urlParams, this.getFragmentAfterLogin()); } if (delegatedAuthSucceeded) { @@ -592,7 +579,7 @@ export default class MatrixChat extends React.PureComponent { return Promise.resolve() .then(() => { return Lifecycle.loadSession({ - fragmentQueryParams: this.props.startingFragmentQueryParams, + urlParams: this.props.urlParams, enableGuest: this.props.enableGuest, guestHsUrl: this.getServerProperties().serverConfig.hsUrl, guestIsUrl: this.getServerProperties().serverConfig.isUrl, @@ -1835,7 +1822,7 @@ export default class MatrixChat extends React.PureComponent { } } - public showScreen(screen: string, params?: { [key: string]: any }): void { + public showScreen(screen: string, params?: Record): void { logger.debug(`showScreen ${screen}`); const cli = MatrixClientPeg.get(); @@ -2267,14 +2254,14 @@ export default class MatrixChat extends React.PureComponent { onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined} onServerConfigChange={this.onServerConfigChange} fragmentAfterLogin={fragmentAfterLogin} - defaultUsername={this.props.startingFragmentQueryParams?.defaultUsername as string | undefined} + defaultUsername={this.props.urlParams?.defaults?.defaultUsername} {...this.getServerProperties()} /> ); } else if (this.state.view === Views.SOFT_LOGOUT) { view = ( diff --git a/apps/web/src/components/structures/MessagePanel.tsx b/apps/web/src/components/structures/MessagePanel.tsx index 629b9d3c7a..a38f264aab 100644 --- a/apps/web/src/components/structures/MessagePanel.tsx +++ b/apps/web/src/components/structures/MessagePanel.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, createRef, type ReactNode, type TransitionEvent } from "react"; +import React, { type JSX, createRef, type ReactNode, type TransitionEventHandler } from "react"; import classNames from "classnames"; import { type Room, @@ -20,6 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { isSupportedReceiptType } from "matrix-js-sdk/src/utils"; import { DateSeparatorView, + ReadMarker, TimelineSeparator, useCreateAutoDisposedViewModel, } from "@element-hq/web-shared-components"; @@ -271,7 +272,7 @@ export default class MessagePanel extends React.Component { private readonly _showHiddenEvents: boolean; private unmounted = false; - private readMarkerNode = createRef(); + private readMarkerNode: HTMLLIElement | null = null; private whoIsTyping = createRef(); public scrollPanel = createRef(); @@ -403,7 +404,7 @@ export default class MessagePanel extends React.Component { // 0: read marker is within the window // +1: read marker is below the window public getReadMarkerPosition(): number | null { - const readMarker = this.readMarkerNode.current; + const readMarker = this.readMarkerNode; const messageWrapper = this.scrollPanel.current?.divScroll; if (!readMarker || !messageWrapper) { @@ -507,29 +508,18 @@ export default class MessagePanel extends React.Component { public readMarkerForEvent(eventId: string, isLastEvent: boolean): ReactNode { if (this.context.timelineRenderingType === TimelineRenderingType.File) return null; - const visible = !isLastEvent && this.props.readMarkerVisible; + const showLine = !isLastEvent && !!this.props.readMarkerVisible; if (this.props.readMarkerEventId === eventId) { - let hr; - // if the read marker comes at the end of the timeline (except - // for local echoes, which are excluded from RMs, because they - // don't have useful event ids), we don't want to show it, but - // we still want to create the
  • for it so that the - // algorithms which depend on its position on the screen aren't - // confused. - if (visible) { - hr =
    ; - } - return ( -
  • - {hr} -
  • + /> ); } else if (this.state.ghostReadMarkers.includes(eventId)) { // We render 'ghost' read markers in the DOM while they @@ -542,28 +532,30 @@ export default class MessagePanel extends React.Component { // case is a little more complex because only some of the items // transition (ie. the read markers do but the event tiles do not) // and TransitionGroup requires that all its children are Transitions. - const hr = ( -
    - ); - // give it a key which depends on the event id. That will ensure that // we get a new DOM node (restarting the animation) when the ghost // moves to a different event. return ( -
  • - {hr} -
  • + ); } return null; } - private collectGhostReadMarker = (node: HTMLElement | null): void => { + private collectReadMarker = (node: HTMLLIElement | null): void => { + this.readMarkerNode = node; + }; + + private collectGhostReadMarker = (node: HTMLHRElement | null): void => { if (node) { // now the element has appeared, change the style which will trigger the CSS transition requestAnimationFrame(() => { @@ -573,7 +565,7 @@ export default class MessagePanel extends React.Component { } }; - private onGhostTransitionEnd = (ev: TransitionEvent): void => { + private onGhostTransitionEnd: TransitionEventHandler = (ev): void => { // we can now clean up the ghost element const finishedEventId = (ev.target as HTMLElement).dataset.eventid; this.setState({ diff --git a/apps/web/src/components/structures/PictureInPictureDragger.tsx b/apps/web/src/components/structures/PictureInPictureDragger.tsx index 2cadc59a7b..d9f472c311 100644 --- a/apps/web/src/components/structures/PictureInPictureDragger.tsx +++ b/apps/web/src/components/structures/PictureInPictureDragger.tsx @@ -37,9 +37,7 @@ interface IChildrenOptions { } interface IProps { - className?: string; children: Array; - draggable: boolean; onDoubleClick?: () => void; onMove?: () => void; } @@ -181,9 +179,6 @@ export default class PictureInPictureDragger extends React.Component { }; private onStartMoving = (event: React.MouseEvent | MouseEvent): void => { - event.preventDefault(); - event.stopPropagation(); - this.mouseHeld = true; this.startingPositionX = event.clientX; this.startingPositionY = event.clientY; @@ -217,9 +212,6 @@ export default class PictureInPictureDragger extends React.Component { private onEndMoving = (event: MouseEvent): void => { if (!this.mouseHeld) return; - event.preventDefault(); - event.stopPropagation(); - this.mouseHeld = false; // Delaying this to the next event loop tick is necessary for click // event cancellation to work @@ -250,7 +242,7 @@ export default class PictureInPictureDragger extends React.Component { return (