Skip to content

Commit cc2a583

Browse files
committed
Merge remote-tracking branch 'upstream/master' into synodim
2 parents a67935c + b6f5843 commit cc2a583

File tree

287 files changed

+4463
-2089
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

287 files changed

+4463
-2089
lines changed

.stylelintrc.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,13 @@ module.exports = {
7070
],
7171
},
7272
],
73+
"property-no-deprecated": [
74+
true,
75+
{
76+
ignoreProperties: ["-webkit-box-orient", "word-wrap"],
77+
},
78+
],
79+
"nesting-selector-no-missing-scoping-root": null,
80+
"no-invalid-position-declaration": null,
7381
},
7482
};

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
Changes in [1.11.110](https://github.com/element-hq/element-web/releases/tag/v1.11.110) (2025-08-27)
2+
====================================================================================================
3+
## ✨ Features
4+
5+
* Hide recovery key when re-entering it while creating or changing it ([#30499](https://github.com/element-hq/element-web/pull/30499)). Contributed by @andybalaam.
6+
* Add `?no_universal_links=true` to OIDC url so EX doesn't try to handle it ([#29439](https://github.com/element-hq/element-web/pull/29439)). Contributed by @t3chguy.
7+
* Show a blue lock for unencrypted rooms and hide the grey shield for encrypted rooms ([#30440](https://github.com/element-hq/element-web/pull/30440)). Contributed by @langleyd.
8+
* Add support for Module API 1.4 ([#30185](https://github.com/element-hq/element-web/pull/30185)). Contributed by @t3chguy.
9+
* MVVM - Introduce some helpers for snapshot management ([#30398](https://github.com/element-hq/element-web/pull/30398)). Contributed by @MidhunSureshR.
10+
11+
## 🐛 Bug Fixes
12+
13+
* A11y: move focus to right panel when opened ([#30553](https://github.com/element-hq/element-web/pull/30553)). Contributed by @florianduros.
14+
* Fix e2e warning icon should be white ([#30539](https://github.com/element-hq/element-web/pull/30539)). Contributed by @florianduros.
15+
* Remove NoOneHere disabled reason. ([#30524](https://github.com/element-hq/element-web/pull/30524)). Contributed by @toger5.
16+
* Fix downloading files with authenticated media API ([#30520](https://github.com/element-hq/element-web/pull/30520)). Contributed by @t3chguy.
17+
* Fix call permissions check confusion around element call ([#30521](https://github.com/element-hq/element-web/pull/30521)). Contributed by @t3chguy.
18+
* Fix line wrap around emoji verification ([#30523](https://github.com/element-hq/element-web/pull/30523)). Contributed by @t3chguy.
19+
* Don't highlight redacted events ([#30519](https://github.com/element-hq/element-web/pull/30519)). Contributed by @t3chguy.
20+
* Fix matrix.to links not being handled in the app ([#30522](https://github.com/element-hq/element-web/pull/30522)). Contributed by @t3chguy.
21+
* Fix issue of new room list taking up the full width ([#30459](https://github.com/element-hq/element-web/pull/30459)). Contributed by @langleyd.
22+
* Fix widget persistence in React development mode ([#30509](https://github.com/element-hq/element-web/pull/30509)). Contributed by @robintown.
23+
* Fix widget initialization in React development mode ([#30463](https://github.com/element-hq/element-web/pull/30463)). Contributed by @robintown.
24+
25+
126
Changes in [1.11.109](https://github.com/element-hq/element-web/releases/tag/v1.11.109) (2025-08-11)
227
====================================================================================================
328
This release supports the upcoming v12 ("hydra") Matrix room version and is necessary to view and participate in these rooms.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
22

33
# Builder
4-
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:a80324457a2c8d09c83ff9edf2bdf71f378d3288de920e68a358bd3c484b8c4a AS builder
4+
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:2d63e0f812d023c4c764e83d7e30dc94949304443ebc372d5c445e63a5ae49c1 AS builder
55

66
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
77
ARG USE_CUSTOM_SDKS=false
@@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
1919
RUN cp /src/config.sample.json /src/webapp/config.json
2020

2121
# App
22-
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:86df552d36eb24c45d3f5becf6423bd056a3fd235d7085fe3d5ea28ba89a8232
22+
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:e61b77b27c8f3124fad6d19e894ca5b603bcaf6a34a2df035511299dfa6fad35
2323

2424
# Need root user to install packages & manipulate the usr directory
2525
USER root

jest.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ const config: Config = {
4040
"^!!raw-loader!.*": "jest-raw-loader",
4141
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
4242
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
43-
// Requires ESM which is incompatible with our current Jest setup
44-
"^@element-hq/element-web-module-api$": "<rootDir>/__mocks__/empty.js",
4543
},
4644
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
4745
collectCoverageFrom: [

package.json

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "element-web",
3-
"version": "1.11.109",
3+
"version": "1.11.110",
44
"description": "Element: the future of secure communication",
55
"author": "New Vector Ltd.",
66
"repository": {
@@ -73,10 +73,10 @@
7373
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
7474
},
7575
"resolutions": {
76-
"**/pretty-format/react-is": "19.1.0",
77-
"@playwright/test": "1.54.1",
78-
"@types/react": "19.1.8",
79-
"@types/react-dom": "19.1.6",
76+
"**/pretty-format/react-is": "19.1.1",
77+
"@playwright/test": "1.54.2",
78+
"@types/react": "19.1.9",
79+
"@types/react-dom": "19.1.7",
8080
"oidc-client-ts": "3.3.0",
8181
"jwt-decode": "4.0.0",
8282
"caniuse-lite": "1.0.30001724",
@@ -86,18 +86,18 @@
8686
},
8787
"dependencies": {
8888
"@babel/runtime": "^7.12.5",
89-
"@element-hq/element-web-module-api": "1.3.0",
89+
"@element-hq/element-web-module-api": "1.4.1",
9090
"@fontsource/inconsolata": "^5",
9191
"@fontsource/inter": "^5",
9292
"@formatjs/intl-segmenter": "^11.5.7",
9393
"@matrix-org/analytics-events": "^0.29.2",
9494
"@matrix-org/emojibase-bindings": "^1.3.4",
9595
"@matrix-org/react-sdk-module-api": "^2.4.0",
9696
"@matrix-org/spec": "^1.7.0",
97-
"@sentry/browser": "^9.0.0",
97+
"@sentry/browser": "^10.0.0",
9898
"@types/png-chunks-extract": "^1.0.2",
9999
"@types/react-virtualized": "^9.21.30",
100-
"@vector-im/compound-design-tokens": "^5.0.0",
100+
"@vector-im/compound-design-tokens": "^6.0.0",
101101
"@vector-im/compound-web": "^8.1.2",
102102
"@vector-im/matrix-wysiwyg": "2.39.0",
103103
"@zxcvbn-ts/core": "^3.0.4",
@@ -135,15 +135,15 @@
135135
"maplibre-gl": "^5.0.0",
136136
"matrix-encrypt-attachment": "^1.0.3",
137137
"matrix-events-sdk": "0.0.1",
138-
"matrix-js-sdk": "37.13.0",
138+
"matrix-js-sdk": "38.0.0",
139139
"matrix-widget-api": "^1.10.0",
140140
"memoize-one": "^6.0.0",
141141
"mime": "^4.0.4",
142142
"oidc-client-ts": "^3.0.1",
143143
"opus-recorder": "^8.0.3",
144144
"pako": "^2.0.3",
145145
"png-chunks-extract": "^1.0.0",
146-
"posthog-js": "1.257.0",
146+
"posthog-js": "1.259.0",
147147
"qrcode": "1.5.4",
148148
"re-resizable": "6.11.2",
149149
"react": "^19.0.0",
@@ -186,7 +186,7 @@
186186
"@babel/preset-typescript": "^7.12.7",
187187
"@babel/runtime": "^7.12.5",
188188
"@casualbot/jest-sonar-reporter": "2.2.7",
189-
"@element-hq/element-call-embedded": "0.13.1",
189+
"@element-hq/element-call-embedded": "0.14.1",
190190
"@element-hq/element-web-playwright-common": "^1.4.4",
191191
"@peculiar/webcrypto": "^1.4.3",
192192
"@playwright/test": "^1.50.1",
@@ -223,9 +223,9 @@
223223
"@types/node-fetch": "^2.6.2",
224224
"@types/pako": "^2.0.0",
225225
"@types/qrcode": "^1.3.5",
226-
"@types/react": "19.1.8",
226+
"@types/react": "19.1.9",
227227
"@types/react-beautiful-dnd": "^13.0.0",
228-
"@types/react-dom": "19.1.6",
228+
"@types/react-dom": "19.1.7",
229229
"@types/react-transition-group": "^4.4.0",
230230
"@types/sanitize-html": "2.16.0",
231231
"@types/semver": "^7.5.8",
@@ -300,8 +300,8 @@
300300
"semver": "^7.5.2",
301301
"source-map-loader": "^5.0.0",
302302
"storybook": "^9.0.12",
303-
"stylelint": "^16.13.0",
304-
"stylelint-config-standard": "^38.0.0",
303+
"stylelint": "^16.23.0",
304+
"stylelint-config-standard": "^39.0.0",
305305
"stylelint-scss": "^6.0.0",
306306
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
307307
"terser-webpack-plugin": "^5.3.9",

playwright/e2e/audio-player/audio-player.spec.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const clickButtonReply = async (tile: Locator) => {
1919
await tile.hover();
2020
await tile.getByRole("button", { name: "Reply", exact: true }).click();
2121
}).toPass();
22+
await expect(tile.page().getByText("Replying", { exact: true })).toBeVisible();
2223
};
2324

2425
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
@@ -39,7 +40,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
3940
// wait for the tile to finish loading
4041
await expect(
4142
page
42-
.locator(".mx_AudioPlayer_mediaName")
43+
.getByTestId("audio-player-name")
4344
.last()
4445
.filter({ hasText: file.split("/").at(-1) }),
4546
).toBeVisible();
@@ -54,12 +55,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
5455
// Check that the audio player is rendered and its button becomes visible
5556
const checkPlayerVisibility = async (locator: Locator) => {
5657
// Assert that the audio player and media information are visible
57-
const mediaInfo = locator.locator(
58-
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo",
59-
);
60-
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName", { hasText: ".ogg" })).toBeVisible(); // extension
61-
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
62-
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
58+
const mediaInfo = locator.getByRole("region", { name: "Audio player" });
59+
await expect(mediaInfo.getByText(".ogg")).toBeVisible(); // extension
60+
await expect(mediaInfo.getByRole("time")).toHaveText("00:01"); // duration
61+
await expect(mediaInfo.getByText("(3.56 KB)")).toBeVisible(); // actual size;
6362

6463
// Assert that the play button can be found and is visible
6564
await expect(locator.getByRole("button", { name: "Play" })).toBeVisible();
@@ -78,7 +77,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
7877
}
7978

8079
// Check the status of the seek bar
81-
expect(await page.locator(".mx_AudioPlayer_seek input[type='range']").count()).toBeGreaterThan(0);
80+
expect(await page.getByRole("region", { name: "Audio player" }).getByRole("slider").count()).toBeGreaterThan(0);
8281

8382
// Enable IRC layout
8483
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
@@ -100,7 +99,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
10099
display: none !important;
101100
}
102101
`,
103-
mask: [page.locator(".mx_AudioPlayer_seek")],
102+
mask: [page.getByTestId("audio-player-seek")],
104103
};
105104

106105
// Take a snapshot of mx_EventTile_last on IRC layout
@@ -186,9 +185,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
186185
await uploadFile(page, "playwright/sample-files/1sec.ogg");
187186

188187
// Assert that the audio player is rendered
189-
const container = page.locator(".mx_EventTile_last .mx_AudioPlayer_container");
188+
const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" });
190189
// Assert that the counter is zero before clicking the play button
191-
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
190+
await expect(container.getByRole("timer")).toHaveText("00:00");
192191

193192
// Find and click "Play" button, the wait is to make the test less flaky
194193
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
@@ -198,7 +197,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
198197
await expect(container.getByRole("button", { name: "Pause" })).toBeVisible();
199198

200199
// Assert that the timer is reset when the audio file finished playing
201-
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
200+
await expect(container.getByRole("timer")).toHaveText("00:00");
202201

203202
// Assert that "Play" button can be found
204203
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
@@ -226,7 +225,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
226225
await uploadFile(page, "playwright/sample-files/1sec.ogg");
227226

228227
// Assert the audio player is rendered
229-
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
228+
await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible();
230229

231230
// Find and click "Reply" button on MessageActionBar
232231
const tile = page.locator(".mx_EventTile_last");
@@ -236,7 +235,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
236235
await uploadFile(page, "playwright/sample-files/1sec.ogg");
237236

238237
// Assert that the audio player is rendered
239-
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
238+
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
240239

241240
// Assert that replied audio file is rendered as file button inside ReplyChain
242241
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']");
@@ -261,23 +260,27 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
261260
await uploadFile(page, "playwright/sample-files/upload-first.ogg");
262261

263262
// Assert that the audio player is rendered
264-
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
263+
await expect(
264+
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
265+
).toBeVisible();
265266

266267
await clickButtonReply(tile);
267268

268269
// Reply to the player with another audio file
269270
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
270271

271272
// Assert that the audio player is rendered
272-
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
273+
await expect(
274+
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
275+
).toBeVisible();
273276

274277
await clickButtonReply(tile);
275278

276279
// Reply to the player with yet another audio file to create a reply chain
277280
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
278281

279282
// Assert that the audio player is rendered
280-
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
283+
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
281284

282285
// Assert that there are two "mx_ReplyChain" elements
283286
await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2);
@@ -313,18 +316,20 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
313316
// On the main timeline
314317
const messageList = page.locator(".mx_RoomView_MessageList");
315318
// Assert the audio player is rendered
316-
await expect(messageList.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
319+
await expect(
320+
messageList.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
321+
).toBeVisible();
317322
// Find and click "Reply in thread" button
318323
await messageList.locator(".mx_EventTile_last").hover();
319324
await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click();
320325

321326
// On a thread
322327
const thread = page.locator(".mx_ThreadView");
323328
const threadTile = thread.locator(".mx_EventTile_last");
324-
const audioPlayer = threadTile.locator(".mx_AudioPlayer_container");
329+
const audioPlayer = threadTile.getByRole("region", { name: "Audio player" });
325330

326331
// Assert that the counter is zero before clicking the play button
327-
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
332+
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
328333

329334
// Find and click "Play" button, the wait is to make the test less flaky
330335
await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible();
@@ -334,7 +339,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
334339
await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible();
335340

336341
// Assert that the timer is reset when the audio file finished playing
337-
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
342+
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
338343

339344
// Assert that "Play" button can be found
340345
await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled();

playwright/e2e/composer/CIDER.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test.describe("Composer", () => {
2828

2929
test.describe("CIDER", () => {
3030
test("sends a message when you click send or press Enter", async ({ page }) => {
31-
const composer = page.getByRole("textbox", { name: "Send a message…" });
31+
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
3232

3333
// Type a message
3434
await composer.pressSequentially("my message 0");
@@ -52,7 +52,7 @@ test.describe("Composer", () => {
5252
});
5353

5454
test("can write formatted text", async ({ page }) => {
55-
const composer = page.getByRole("textbox", { name: "Send a message…" });
55+
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
5656

5757
await composer.pressSequentially("my bold");
5858
await composer.press(`${CtrlOrMeta}+KeyB`);
@@ -68,7 +68,7 @@ test.describe("Composer", () => {
6868
await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click();
6969

7070
await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker
71-
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message
71+
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Send message
7272

7373
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
7474
});
@@ -79,7 +79,7 @@ test.describe("Composer", () => {
7979
});
8080

8181
test("only sends when you press Control+Enter", async ({ page }) => {
82-
const composer = page.getByRole("textbox", { name: "Send a message…" });
82+
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
8383
// Type a message and press Enter
8484
await composer.pressSequentially("my message 3");
8585
await composer.press("Enter");

0 commit comments

Comments
 (0)