Compare commits

...

73 Commits

Author SHA1 Message Date
RiotRobot f72e09173b v1.12.13
Docker / Docker Buildx (push) Failing after 57s
2026-03-24 11:26:49 +00:00
RiotRobot b49a29f3df Upgrade dependency to matrix-js-sdk@41.2.0 2026-03-24 11:23:51 +00:00
ElementRobot 09f56ed3b6 [Backport staging] Fix soft crash of room list when trying to open a room (#32872)
* Fix soft crash of room list when trying to open a room (#32864)

* fix: soft crash of room list trying to get item vm

* test: add test to check roomMap recovery and cleared when needed

(cherry picked from commit 9358096ac6)

* test: fix room list vm usage in test

https://github.com/element-hq/element-web/pull/32819 has renamed the vm
but this PR isn't in the base branch of the backport

---------

Co-authored-by: Florian Duros <florianduros@element.io>
Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
2026-03-20 15:44:40 +00:00
RiotRobot 7b396e8de7 v1.12.13-rc.0
Docker / Docker Buildx (push) Failing after 50s
2026-03-17 11:43:46 +00:00
RiotRobot 3c87309f7d Upgrade dependency to matrix-js-sdk@41.2.0-rc.0 2026-03-17 11:41:10 +00:00
Robin 6339bcda15 Upgrade Element Call for new picture-in-picture designs (#32816) 2026-03-17 08:57:37 +00:00
YONGJAE LEE(이용재) 68070b2e97 Fix E2E test quality issues: always-passing assertions, unawaited checks, and dead code (#32801)
* Fix E2E test quality issues: always-passing assertions, unawaited checks, and dead code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* apply review

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 18:00:22 +00:00
Florian Duros ee5d2609df Room list: add sections to shared components (#32735)
* feat: add section header

* refactor: remove index and role related to list box from RoomListItemView

* feat: add wrapper to RoomListItemView to handle different accessiblity pattern

* feat: add section support to VirtualizedRoomListView

* feat: add sections support to RoomListView

* test: add screenshot for sections header

* test: add/update screenshots for sections

* feat: force flat list on view model

This is an intermediary step before implementing sections in the vm. We
force the flat list but we use the underneath the view supporting
sections.

* test: update RoomListViewModel test

* test: fix breaking test

* chore: rename `getSectionViewModel` to `getSectionHeaderViewModel`

* chore: add missing `RoomListItemAccessibilityWrapper` export

* chore: merge `react` imports

* chore: simplify and add comment to `getItemKey` and `getHeaderKey`

* chore add comments to `getItemComponent` variants

* chore: fix typo in example doc
2026-03-16 16:40:12 +00:00
Andy Balaam d18aa31d7d Change 'Verify' to 'Continue' in verify this device toast (#32813) 2026-03-16 15:07:57 +00:00
Andy Balaam 35babd83d6 Make the DeviceListener listen to KeyBackupDecryptionKeyCached events (#32811)
Fixes #31916
2026-03-16 13:19:42 +00:00
Andy Balaam cace9d918f Screenshot and snapshot tests for event list summaries (#32788)
* Playwright tests for the event list summary

* Add snapshots for event summary tests
2026-03-16 12:16:56 +00:00
Valere Fedronic 3e88689d69 feat: Devtool for sticky events MSC4354 (#32741)
* feat: Devtool for sticky events MSC4354

* Update devtool snapshot to add sticky state devtool

* Update devtool playwright screenshot

* review: Use UserFriendlyError instead or Error

* review: fix docs

* review: remove css in js, remove js hover tracking

* review: use keyboard enums

* add a check to see if homeserver supports sticky events

* fixup: prettier

* review: No static inline styles

* review: use cpd spacing / border / color values

* cleanup keyboard code

* Fix unsupported alert look

* review: proper useState usage (no | null)

* review: useAsyncMemo instead of useEffect

* review: use useTypedEventEmitterState

* fix: better support for empty string event type

* review: remove redundant expired state
2026-03-16 10:54:16 +00:00
Will Hunt 3b4027846d Port URL Preview components to MVVM (#32525)
* Port over linkifyJS to shared-components.

* Drop rubbish

* update lock

* quickfix test

* drop group id

* Modernize tests

* Remove stories that aren't in use.

* Complete working version

* Add copyright

* tidy up

* update lock

* Update snaps

* update snap

* undo change

* remove unused

* More test updates

* fix typo

* fix margin on preview

* move margin block

* snapupdate

* prettier

* Port url preview logic to a view model.

* More fiddling with VM logic

* Note to self

* Refactor away into a shared component.

* Even more lovely lovely code that makes it look prettier

* translation cleanup

* Even more stuff that I need to fix yay

* Remove .last-run.json

* Update snaps

* Ensure we set showUrlPreview

* Cleanup tests

* lint + add png support

* Add a label

* Cleanup

* Add snaps

* Update snaps

* update playwright

* Refactors

* update snap

* Add missing snap

* Remove editing code (we check this in a better way in componentDidUpdate)

* Add README

* fix the one unused import

* Style shuffling

* Update vis tests

* Finally fix the tooltip

* Remove unused prop

* Add some padding

* fix lint issue

* Design improvements

* new screens

* Update snaps

* Fix CSS specificity

* Remove stale screenshot

* Rename function to match reality

* Port viewmodel tests to snapshots

* finish documenting types

* Stop being dangerous

* Use Linkify+decode for description

* Remove ability for VM to do linkifying.

* Port over linkifyJS to shared-components.

* Drop rubbish

* update lock

* quickfix test

* drop group id

* Modernize tests

* Remove stories that aren't in use.

* Complete working version

* Add copyright

* tidy up

* update lock

* Update snaps

* update snap

* undo change

* remove unused

* More test updates

* fix typo

* fix margin on preview

* move margin block

* snapupdate

* prettier

* cleanup a test mistake

* Fixup sonar issues

* Don't expose linkifyjs to applications, just provide helper functions.

* Add story for documentation.

* remove $

* Use a const

* typo

* cleanup var name

* remove console line

* Changes checkpoint

* Convert to context

* Revert unrelated change.

* more cleanup

* Add a test to cover ignoring incoming data elements

* Make tests happy

* Update tests for LinkedText

* Underlines!

* fix lock

* remove unused linkify packages

* import move

* Remove mod to remove underline

* undo

* fix snap

* another snapshot fix

* More cleanup

* Tidy up based on review.

* fix story

* Pass in args

* update snap

* cleanup

* use source image

* oops

* remove client peg

* Remove unused state

* tidy up code

* Ensure we update the preview when the event content may have changed.

* s/global/globalThis/

* Ensure we don't stretch images

* Update screenshots

* Cleanup
2026-03-16 10:05:34 +00:00
Will Hunt b54e4e3b98 Update beta documentation (#32757)
* Add missing feature_notification_settings2

* Update beta docs with stronger restrictions

* a note

* Update betas.md

* Update betas.md
2026-03-16 09:53:34 +00:00
rbondesson d791e3fe8a Refactor MFileBody using MVVM and move to shared-components (#32730)
* Refactor MFileBody using MVVM and move to shared component

* Simplyfing rendering properties

* Create a first version of view model for the component

* Simplifying component properties and make it possible to override module css using data-* attributes

* Create a MBodyFactory in element-web and use it to render MFileBodyView from MessageEvent

* Use <MediaBody instead of <button to support legacy rendering

* Updated styling and comments

* Refactoring className from snapshot to component property

* Rename MFileBody* to FileBody*

* Rename MFileBody* to FileBody*

* Refactoring render branches to allow for displaying nothing

* Fix styling issues

* Fix lint errors

* Fix for css selectors in playwright tests

* Remove the MFileBody component and change all callers to use MBodyFactory:FileBodyView

* Remove unused strings in element-web

* Revert to render text in story iframes

* Fix for prettier error

* Fix playwright test css selectors

* Apply legacy styling in element-web

* Add legacy styling for mx_MFileBody

* Restore file

* Change from <div to <button

* Calculate span width ad update screenshots

* Remove width calculation and update snapshots

* Fix for letter-spacing and better content in story

* Updated playwright screenshots

* Updated snapshots

* Fixing Sonar errors/warnings

* Removed extra parentheses

* Changes after review

* Change border-radius to px and updated snapshots

* Fix typo in description

* And another typo fix

* Changes after review
2026-03-16 08:47:23 +00:00
Florian Duros 394356c4df fix: ugly remove button in local address list (#32798) 2026-03-13 16:12:31 +00:00
Will Hunt 772a443486 Remove automatic rageshakes on UTD labs feature (#32778)
* Remove automatic rageshakes on UTD feature

* remove unused setting name

* Remove unused lab group

* Reduce count by 1
2026-03-13 13:55:30 +00:00
David Baker 09bbf796dc Add support for Widget & Room Header Buttons module APIs (#32734)
* Add support for Widget & Room Header Buttons module APIs

To support https://github.com/element-hq/element-modules/pull/217

* Update for new api

* Test addRoomHeaderButtonCallback

* Extra mock api

* Test for widgetapi

* Convert enum

* Convert other enum usage

* Add tests for widget context menu move buttons

Which have just changed because of the enum

* Add tests for moving the widgets

* Fix copyright

Co-authored-by: Florian Duros <florianduros@element.io>

* Update module API

* A little import/export

---------

Co-authored-by: Florian Duros <florianduros@element.io>
2026-03-13 13:44:18 +00:00
Florian Duros 86692ce0a7 doc: update developer guide regarding new components (#32795) 2026-03-13 13:17:48 +00:00
Will Hunt 0ca4f8013b Check timezone initially (#32764) 2026-03-13 11:50:25 +00:00
Will Hunt a73335168d Remove automaticErrorReporting labs feature (#32781)
* Disable changing automaticErrorReporting unless sentry is configured.

* Ensure we reload on change

* Remove automaticErrorReporting labs flag

* remove stray import

* I thought I had killed you

* poke ci
2026-03-13 11:29:19 +00:00
Florian Duros 635b0e6fe2 test: increase timeout of flaky wysiwyg composer (#32794) 2026-03-13 10:20:54 +00:00
Richard van der Hoff 8fe2e72245 Fix nx configuration to actually run type linter (#32776)
* do not indirect tsc commands via tsc

Currently, `nx lint:types` is configured to indirect its `tsc` incantations via
pnpm. As far as I can tell, this is unnecessary; worse, it means we seem to hit
a pnpm reentrancy bug which causes pnpm not to run those commands at all if the
top level pnpm has `--if-present`.

* Reinstate indirection via pnpm

... to evade knip.
2026-03-13 09:59:16 +00:00
Florian Duros 40a322ac05 Room list: add a grouped virtualized list to shared components (#32566)
* refactor: extract most of the logic from the virtualized list

The VirtualizedList component is renamed FlatVirtualizedList and most of
the logic is extracted. In order to prepare the introduction of the
GroupedVirtualizedList which will share most of the behaviour.

* refactor: use `FlatVirtualizedList` instead of `VirtualizedList`

* feat: add grouped virtualized list to shared components

* feat: add accessiblity helps for virtualized list

* test: use one test suite for the two virtualized lists

* test: update storybook screenshots

* feat: add keyboard navigation on header

* test: make a11y test pass

* chore: delete old screenshot

* doc: a11y docs to list stories

* chore: fix copyright
2026-03-13 09:53:09 +00:00
ElementRobot 5f92215ead [create-pull-request] automated change (#32792)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2026-03-13 06:39:14 +00:00
Will Hunt c02db4ebb8 Port over linkifyJS to shared-components. (#32731)
* Port over linkifyJS to shared-components.

* Drop rubbish

* update lock

* quickfix test

* drop group id

* Modernize tests

* Remove stories that aren't in use.

* Complete working version

* Add copyright

* tidy up

* update lock

* Update snaps

* update snap

* undo change

* remove unused

* More test updates

* fix typo

* fix margin on preview

* move margin block

* snapupdate

* prettier

* cleanup a test mistake

* Fixup sonar issues

* Don't expose linkifyjs to applications, just provide helper functions.

* Add story for documentation.

* remove $

* Use a const

* typo

* cleanup var name

* remove console line

* Changes checkpoint

* Convert to context

* Revert unrelated change.

* more cleanup

* Add a test to cover ignoring incoming data elements

* Make tests happy

* Update tests for LinkedText

* Underlines!

* fix lock

* remove unused linkify packages

* import move

* Remove mod to remove underline

* undo

* fix snap

* another snapshot fix

* Tidy up based on review.

* fix story

* Pass in args
2026-03-12 15:54:01 +00:00
Will Hunt d38eb4fdb4 Fix WidgetPip test having an incomplete mock (#32774)
* Fixup type

* no need to type this

* use createRef
2026-03-12 13:37:45 +00:00
Zack 9fa8b34ebe Move PinnedMessageBadge To Shared Components (#32768)
* refactor: move pinned message badge into shared-components

* Update lint errors

* Fix for Vitest issue with CI on shared components

* Update related to the pr comments and requests

* Prettier Fix

* Update, remove view since it was just moved and not actually MVVMed

* Removed view suffix since it was only moved and not mvvmed

* Update snapshots

* Removal of unused screenshots

* Added Image that we need
2026-03-12 13:34:45 +00:00
Florian Duros a1939f69ee chore: remove release announcement labs flag (#32775) 2026-03-12 13:22:07 +00:00
ElementRobot ba1b76da39 [create-pull-request] automated change (#32767)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2026-03-11 06:39:18 +00:00
Timo 6d99678ade Redesign widget pip and move into shared component (#32654)
* redesign widget pip and move into shared component

* fix onBackClick handler

* fix ci

* Update README.md prepare -> prepack

* add vm tests

* Update WidgetPipView.stories.tsx

* fix tests

* playwright tests

* fix test id

* remove unused files (reappeared after rebase)

* update storybook screenshot tests

* update playwright tests

* adjust padding

* review

* comment and docstring corrections

* fix imports and `this.props`

* fix double `complementary` item

* add WidgetPipView tests and revmoe `setViewingRoom` from
WidgetPipViewModelInterface.

* add doc sting to `setViewingRoom`

* Update RoomStatusBarView.test.tsx

* fix copyright

* Update RoomView-test.tsx.snap

* revert accidental Copyright year changes

* update snapshot RoomView-test
2026-03-10 16:59:51 +00:00
RiotRobot 652b9f5b5b Reset matrix-js-sdk back to develop branch 2026-03-10 14:06:59 +00:00
RiotRobot 3bd14239b1 Merge branch 'master' into develop 2026-03-10 14:06:27 +00:00
Michael Telatynski 35afc2fdf8 Implement customisations & login component Module API 1.11.0 (#32687)
* Update to Module API v1.11.0

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove stale state field to make CQL happy

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Bump npm dep

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update comment

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-10 13:30:48 +00:00
Michael Telatynski 78b40a6fed Build typedoc before test:storybook & test:storybook:update (#32719)
* Build typedoc before test:storybook & test:storybook:update

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix doc

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-10 11:22:20 +00:00
Michael Telatynski 2e0adc5832 Add zizmor CI & make it happy (#32717)
* Add zizmor CI & make it happy

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix zizmor job

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-09 16:52:04 +00:00
Michael Telatynski 095b407dae Realign MessageActionBar to Figma designs (#32722)
* Realign MessageActionBar to Figma designs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate style based on review

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Move tooltips to above MAB

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update event bubble tile style

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update apps/web/res/css/views/messages/_MessageActionBar.pcss

Co-authored-by: Florian Duros <florianduros@element.io>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Florian Duros <florianduros@element.io>
2026-03-09 10:29:15 +00:00
Zack 8d076c897d Refactor EventContentBody to shared-components (#31914)
* Init of refactoring of eventcontentbody

* update stories css by copying css from element x to shared components

* Replaced old component EventContentBody with newly created mmvm component EventContentBodyViewModel

* Refactor TextualBody and EditHistoryMessage to properly manage EventContentBodyViewModel

* generated snapshot after vitest

* Update import placement for eslint to pass CI

* Fixed lint warnings

* Update css for codeblock to represent js highlight

* test: add EventContentBodyViewModel snapshot coverage

* fix: pass content ref to EventContentBodyView for link previews

* Fix: return to old code that passed tests

* Added storybook snapshots

* Removal of old component that is being unused

* Update snapshot

* Fix missing enableBigEmoji and shouldShowPillAvatar settings in EventContentBodyViewModel

* update snapshot

* narrow setProps to mutable fields and skip no-op snapshot recomputes

* Update Snapshots

* replace EventContentBodyViewModel setProps with explicit setters and update call sites

* render body in view and keep parser/replacer in snapshot

* Eslint Restruct

* Eslint Restructure

* Removed unused function, moved to shared component

* Remove Unused Module (Moved To Shared Component)

* Disable EventContent-body Test to check weather it fixes CI

* Enable EventContentBody Tests

* Remove EventTest

* Update Include in Vitest

* Added EventContentBody test

* Update Package.json

* Update Lockfile

* Update dependencies

* update lockfile

* ptimize EventContentBodyViewModel to recompute/merge only changed snapshot fields

* Update snapshots

* setEventContent and setStripReply run whenever the existing update block runs

* defined arrow functions for undefined runtime issues that might occur.

* Update test cases

* Update packages/shared-components/src/message-body/EventContentBody/EventContentBodyView.tsx

Co-authored-by: R Midhun Suresh <rmidhunsuresh@gmail.com>

* Update packages/shared-components/src/message-body/EventContentBody/EventContentBodyView.tsx

Co-authored-by: R Midhun Suresh <rmidhunsuresh@gmail.com>

* move big-emoji and pill-avatar setting watchers into EventContentBodyViewModel

* Update packages/shared-components/src/message-body/EventContentBody/index.tsx

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Update packages/shared-components/src/message-body/EventContentBody/EventContentBodyView.tsx

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Update packages/shared-components/src/message-body/EventContentBody/EventContentBody.test.tsx

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Update packages/shared-components/src/message-body/EventContentBody/EventContentBody.stories.tsx

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Update packages/shared-components/src/message-body/EventContentBody/EventContentBodyView.tsx

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Update packages/shared-components/src/message-body/EventContentBody/EventContentBodyView.tsx

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Fix dubblicate variables

* clarify applyReplacerOnString input/replacer params

* Added memo to the view

* Prettier Fix

* Update apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts

Co-authored-by: Florian Duros <florian.duros@ormaz.fr>

* Added compund variables instead of reguler values

* Added boolean default values

* remove redundant setting props from TextualBody and EditHistoryMessage

* Prettier FIx

* replace MatrixClientPeg usage with `client: MatrixClient | null` passed from context

* TextualBody now passes EventContentBodyViewModel `client` from RoomContext.

* Remove redundant as prop from EventContentBody VM usage

* Normalize EventContentBodyViewModel renderer flags to booleans

---------

Co-authored-by: R Midhun Suresh <rmidhunsuresh@gmail.com>
Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
2026-03-09 09:58:05 +00:00
Florian Duros 3e77974fa0 Room list: remove unused sorting ManualAlgorithm (#32724)
* chore: remove unused `ManualAlgorithm`

In the old room list ui, we can"t manually sort the rooms or the
sections. Neither in a menu or by drag and drop

* fix: add `undefined` to `tagRoom` signature since the code allows it

* test: add tests to `RoomListActions`

* chore: remuve unuseful comment

* refactor: remove unused ``newIndex

* doc: remove typing in tsdoc
2026-03-09 09:52:52 +00:00
ElementRobot 00dd0c48ba Localazy Download (#32714)
* [create-pull-request] automated change

* Use a dedicated string for public access in settings.

* Fix tests due to copy changes.

* Fix tests due to copy changes.

* Fix tests due to copy changes.

* Fix tests due to copy changes.

* Fix tests due to copy changes.

* Updated visual tests screenshots due to copy changes.

* Updated visual tests screenshots due to copy changes.

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: mxandreas <andreass@element.io>
2026-03-09 09:43:00 +00:00
Michael Telatynski f550b41724 Fix playwright html report missing in CI (#32748)
* Fix playwright html report missing in CI

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update paths for playwright html report in workflow

* Add if-no-files-found option to upload step

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-06 15:43:23 +00:00
Will Hunt b523237395 Playwright tests for linkification (#32737)
* Add linkification tests for messages / topic.

* Add test for permalinks

* Migrate permalink test

* fixup test

* Fix tests
2026-03-06 13:02:31 +00:00
Michael Telatynski 0fc4d4c256 Fix expand space panel button not being shown on keyboard focus (#32746)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-06 11:23:03 +00:00
rbondesson e4ed5240e7 Fix playwright script in shared components (#32743) 2026-03-06 09:50:51 +00:00
Michael Telatynski 32037b0135 Make knip happier (#32721)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-05 21:42:08 +00:00
Michael Telatynski b2674e6fa4 Handle linking to non-pnpm deps in pnpm-link (#32720)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-05 21:41:31 +00:00
Florian Duros cd3b9efe50 chore: fix prettier issue on test file (#32733) 2026-03-05 15:32:52 +00:00
David Baker c7092955b1 Add note against using force push in PRs (#32729)
* Add note against using force push in PRs

Because apparently this wasn't here which probably explains why I have to keep asking people not to force push.

* Okay prettier
2026-03-05 15:12:47 +00:00
Andy Balaam 1c2441bc76 Reset key storage if restoring from Recovery encounters the wrong decryption key (#32668)
* Set up the MatrixClient before each RecoveryPanelOutOfSync test

Without this, we can't override MatrixClient methods until we've called
`renderComponent`, which is awkward.

* Actually test that we load the decryption key in RecoveryPanelOutOfSync

It turns out the existing test didn't actually go down the expected code
path and call loadSessionBackupPrivateKeyFromSecretStorage.

* Reset key storage if restoring from Recovery encounters the wrong decryption key

Fixes https://github.com/element-hq/element-web/issues/31793

Depends on https://github.com/matrix-org/matrix-js-sdk/pull/5202

When we try to load the key storage decryption key from Recovery, but we
find that it does not match the public key of the current key storage
backup, create a new key storage backup.
2026-03-05 10:47:50 +00:00
Florian Duros 9035da48a2 refactor: listen to RoomEvent.Tags instead of relying on the old room list store to update the summary card (#32728) 2026-03-05 10:35:59 +00:00
rbondesson 83d732d60e Refactor className and children to component properties instead och view model snapshots in shared components (#32711)
* Refactor className? to component property in EncryptionEventView

* Refactor extraClassNames to default react className as component property for DecryptionFailureBodyView

* Refactor className to component property for MessageTimestampView

* Refactor className and children to component properties for ReactionsRowButton

* Refactor className to component property for DisambiguatedProfile

* Refactor className to a component property in DateSeparatorView

* Fix for lint errors and EncryptionEventView unsupported icon color

* EncryptionEventView fix for icon color css specificity/order
2026-03-05 08:36:45 +00:00
Florian Duros 1963f268aa Room list: move MessagePreviewStore and previews into its own directory (#32710)
* refactor: move `MessagePreviewStore` and previews into its own directory

The `MessagePreviewStore` is used widly and not only by the room list.
Moving to its own folder to be able to remove old room list later with
less friction

* test: add more tests
2026-03-04 17:40:12 +00:00
Florian Duros 1c66f0ba01 Room list: extract getTagsForRoom from old room list store (#32716)
* refactor: extract `getTagsForRoom` from old rls

`getTagsForRoom` doesn't rely on the rls state. We can extract it safely
to an external function.

The function is not moved into the rls v3 because the rls is for the
room list and not for other ui elements.

* refactor: remove dead code

* test: add more tests for `getTagsForRoom`

* refactor: `getTagsForRoom` in old rls

* doc: add missing tsdoc for `room`
2026-03-04 17:14:48 +00:00
renovate[bot] ac37bebf22 Update dependency caniuse-lite to v1.0.30001774 (#32549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 19:42:28 +00:00
renovate[bot] 699a8759c5 Update ghcr.io/element-hq/synapse:develop Docker digest to b256d74 (#32695)
* Update ghcr.io/element-hq/synapse:develop Docker digest to b256d74

* Update screenshot due to new API availability on Synapse

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-03 19:42:09 +00:00
Florian Duros 93dc9fedc8 Room list: remove direct usage of the old rls in rls v3 (#32692)
* refactor: move `DefaultTagID` and `TagID` to rls v3

Move the enum and type in rls v3 and update imports

* refactor: move `getChangedOverrideRoomMutePushRules` from rls to rls v3

* refactor: replace `VisiblityProvider` by `isRoomVisible` and move it to rls v3
2026-03-03 19:25:20 +00:00
renovate[bot] 49dffe83cc Update dependency @vector-im/compound-design-tokens to v6.10.1 (#32698)
* Update dependency @vector-im/compound-design-tokens to v6.10.1

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2026-03-03 19:17:40 +00:00
renovate[bot] 2c9f55cbea Update npm non-major dependencies (#32702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 18:19:50 +00:00
renovate[bot] b6dbe1c259 Update pnpm to v10.30.3 (#32703)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 17:52:55 +00:00
renovate[bot] e19e338aa9 Update dependency @sentry/webpack-plugin to v5 (#32704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 17:41:57 +00:00
renovate[bot] 3f69aec64a Update dependency css-minimizer-webpack-plugin to v8 (#32706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 17:41:05 +00:00
David Langley cea684c065 Implement new widget permissions module api (#32565)
* Add widget lifecycle API at top level

* Integrate while still falling back to the legacy api

* Remove WidgetKind

* Update module api

to the one that includes the new widget lifecycle api

* lint

* Make preload checks easier to understand

- Have single code path for preload checks.
- Remove duplicated logic for preapproveIdentity check
- Fix headers

* lint
2026-03-03 17:06:39 +00:00
renovate[bot] 611e924dc2 Update dependency wrap-ansi to v10 (#32707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 17:04:28 +00:00
renovate[bot] e439d6adc1 Update eslint-plugins (#32701)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 16:51:43 +00:00
renovate[bot] 255f9f03e9 Update dependency wrap-ansi-cjs to v10 (#32708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:52:23 +00:00
renovate[bot] 7a2f092c6d Update GitHub Artifact Actions (#32709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:50:22 +00:00
renovate[bot] 580949038c Update dependency copy-webpack-plugin to v14 (#32705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:41:05 +00:00
renovate[bot] ba0365d04e Update storybook to v10.2.13 (#32699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:40:40 +00:00
renovate[bot] b0e12b829f Update typescript (#32700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:40:39 +00:00
renovate[bot] 13b070d03d Update nginxinc/nginx-unprivileged:alpine-slim Docker digest to 800307a (#32696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:40:25 +00:00
renovate[bot] 729f0df6d5 Update ghcr.io/element-hq/matrix-authentication-service:main Docker digest to baa02c3 (#32694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:38:42 +00:00
renovate[bot] c6d76fcd91 Update Node.js to d83f76e (#32697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:34:05 +00:00
renovate[bot] 11799d4068 Update actions/checkout digest to de0fac2 (#32693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 14:31:42 +00:00
580 changed files with 22811 additions and 7891 deletions
+3 -1
View File
@@ -1,6 +1,8 @@
name: Backport
on:
pull_request_target:
# Privilege escalation necessary to enable backporting PRs from forks
# 🚨 We must not execute any checked out code here.
pull_request_target: # zizmor: ignore[dangerous-triggers]
types:
- closed
- labeled
+3 -1
View File
@@ -44,6 +44,8 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
@@ -67,7 +69,7 @@ jobs:
run: VERSION=$(scripts/get-version-from-git.sh) pnpm build
- name: Upload Artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: webapp-${{ matrix.image }}
path: apps/web/webapp
+4 -2
View File
@@ -15,6 +15,8 @@ jobs:
VERSION: ${{ github.ref_name }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Download package
working-directory: apps/web
@@ -67,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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: element-web.deb
path: apps/web/element-web.deb
@@ -75,7 +77,7 @@ jobs:
- name: Publish to packages.element.io
if: github.event.release.prerelease == false
uses: element-hq/packages.element.io@master
uses: element-hq/packages.element.io@master # zizmor: ignore[unpinned-uses]
with:
file: apps/web/element-web.deb
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
+6 -2
View File
@@ -29,6 +29,8 @@ jobs:
R2_PUBLIC_URL: "https://element-web-develop.element.io"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
@@ -58,7 +60,7 @@ jobs:
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
working-directory: apps/web
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: webapp
path: apps/web/dist/develop.tar.gz
@@ -134,4 +136,6 @@ jobs:
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
- run: |
echo "Deployed to ${{ steps.cfp.outputs.url }}" >> $GITHUB_STEP_SUMMARY
echo "Deployed to ${STEPS_CFP_OUTPUTS_URL}" >> $GITHUB_STEP_SUMMARY
env:
STEPS_CFP_OUTPUTS_URL: ${{ steps.cfp.outputs.url }}
+2 -1
View File
@@ -19,13 +19,14 @@ jobs:
contents: read
actions: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
# We need to fetch all branches and commits so that Nx affected has a base to compare against.
fetch-depth: 0
# reduce the size of the checkout with tree filtering,
# see https://github.blog/open-source/git/get-up-to-speed-with-partial-clone-and-shallow-clone/
filter: tree:0
persist-credentials: false
- name: Prepare nx
uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4
+2
View File
@@ -35,6 +35,8 @@ jobs:
SITE: ${{ inputs.site || 'staging.element.io' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Load GPG key
run: |
+1
View File
@@ -23,6 +23,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0 # needed for docker-package to be able to calculate the version
persist-credentials: false
- name: Install Cosign
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3
+3
View File
@@ -21,17 +21,20 @@ jobs:
with:
repository: element-hq/element-desktop
path: element-desktop
persist-credentials: false
- name: Fetch element-web
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
path: element-web
persist-credentials: false
- name: Fetch matrix-js-sdk
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: matrix-org/matrix-js-sdk
path: matrix-js-sdk
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
@@ -2,7 +2,9 @@
# taking the artifact and uploading it to Netlify for easier viewing
name: Upload End to End Test report to Netlify
on:
workflow_run:
# 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"]
types:
- completed
@@ -25,7 +27,7 @@ jobs:
actions: read
steps:
- name: Download HTML report
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
+17 -10
View File
@@ -54,6 +54,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: element-hq/element-web
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
@@ -78,7 +79,7 @@ jobs:
run: VERSION=$(scripts/get-version-from-git.sh) pnpm build
- name: Upload Artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: webapp
path: apps/web/webapp
@@ -132,7 +133,7 @@ jobs:
repository: element-hq/element-web
- name: 📥 Download artifact
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
name: webapp
path: apps/web/webapp
@@ -160,11 +161,13 @@ jobs:
- 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
# We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else
@@ -172,13 +175,15 @@ jobs:
working-directory: apps/web
run: |
pnpm playwright test \
--shard "${{ matrix.runner }}/${{ needs.build.outputs.num-runners }}" \
--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) }}
- name: Upload blob report to GitHub Actions Artifacts
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }}
path: apps/web/blob-report
@@ -188,7 +193,7 @@ jobs:
name: Downstream Playwright tests [element-modules]
needs: build
if: inputs.skip != true && github.event_name == 'merge_group'
uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main
uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main # zizmor: ignore[unpinned-uses]
with:
webapp-artifact: webapp
@@ -220,15 +225,16 @@ jobs:
- name: Download blob reports from GitHub Actions Artifacts
if: inputs.skip != true
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
pattern: all-blob-reports-*
path: all-blob-reports
path: apps/web/all-blob-reports
merge-multiple: true
- name: Merge into HTML Report
if: inputs.skip != true
run: pnpm playwright merge-reports --reporter=html,./apps/web/playwright/flaky-reporter.ts,@element-hq/element-web-playwright-common/lib/stale-screenshot-reporter.js ./all-blob-reports
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
env:
# Only pass creds to the flaky-reporter on main branch runs
GITHUB_TOKEN: ${{ github.ref_name == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }}
@@ -236,11 +242,12 @@ jobs:
# Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected
- name: Upload HTML report
if: always() && inputs.skip != true
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: html-report
path: playwright-report
path: apps/web/playwright-report
retention-days: 14
if-no-files-found: error
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
+1 -1
View File
@@ -7,7 +7,7 @@ permissions:
pull-requests: write # needed to auto-approve PRs
jobs:
download:
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_download.yaml@main
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_download.yaml@6eda3835118f3bc3fb658a1a3c20b7da9d16ae42
with:
packageManager: pnpm
secrets:
+1 -1
View File
@@ -9,6 +9,6 @@ on:
permissions: {} # No permissions needed
jobs:
upload:
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_upload.yaml@main
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_upload.yaml@6eda3835118f3bc3fb658a1a3c20b7da9d16ae42
secrets:
LOCALAZY_WRITE_KEY: ${{ secrets.LOCALAZY_WRITE_KEY }}
+4 -2
View File
@@ -2,7 +2,9 @@
# and uploading it to netlify
name: Upload Preview Build to Netlify
on:
workflow_run:
# Privilege escalation necessary to publish to Netlify
# 🚨 We must not execute any checked out code here.
workflow_run: # zizmor: ignore[dangerous-triggers]
workflows: ["Build"]
types:
- completed
@@ -28,7 +30,7 @@ jobs:
Exercise caution. Use test accounts.
- name: 📥 Download artifact
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
+4 -2
View File
@@ -1,13 +1,15 @@
name: Pull Request
on:
pull_request_target:
# Privilege escalation necessary access members of the review teams
# 🚨 We must not execute any checked out code here, and be careful around use of user-controlled inputs.
pull_request_target: # zizmor: ignore[dangerous-triggers]
types: [opened, edited, labeled, unlabeled, synchronize]
merge_group:
types: [checks_requested]
permissions: {}
jobs:
action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop # zizmor: ignore[unpinned-uses]
permissions:
pull-requests: write
secrets:
+1 -1
View File
@@ -9,4 +9,4 @@ jobs:
draft:
permissions:
contents: write
uses: matrix-org/matrix-js-sdk/.github/workflows/release-drafter-workflow.yml@develop
uses: matrix-org/matrix-js-sdk/.github/workflows/release-drafter-workflow.yml@develop # zizmor: ignore[unpinned-uses]
+1 -1
View File
@@ -7,7 +7,7 @@ concurrency: ${{ github.repository }}-${{ github.workflow }}
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
merge:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-gitflow.yml@develop
uses: matrix-org/matrix-js-sdk/.github/workflows/release-gitflow.yml@develop # zizmor: ignore[unpinned-uses]
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
with:
+1 -1
View File
@@ -14,7 +14,7 @@ concurrency: ${{ github.workflow }}
permissions: {}
jobs:
release:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop
uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop # zizmor: ignore[unpinned-uses]
permissions:
contents: write
issues: write
+4 -1
View File
@@ -27,7 +27,7 @@ jobs:
- matrix-org/matrix-js-sdk
- element-hq/element-web
- element-hq/element-desktop
uses: matrix-org/matrix-js-sdk/.github/workflows/release-checks.yml@develop
uses: matrix-org/matrix-js-sdk/.github/workflows/release-checks.yml@develop # zizmor: ignore[unpinned-uses]
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
with:
@@ -50,6 +50,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
persist-credentials: true
- name: Checkout Element Web
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
if: inputs.element-web
@@ -60,6 +61,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
persist-credentials: true
- name: Checkout Matrix JS SDK
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
if: inputs.matrix-js-sdk
@@ -70,6 +72,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
persist-credentials: true
- name: Prepare Git
run: |
@@ -14,6 +14,8 @@ jobs:
steps:
- name: 🧮 Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: 🔧 Set up node environment
@@ -13,6 +13,8 @@ jobs:
steps:
- name: 🧮 Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: 🔧 Pnpm cache
@@ -29,7 +31,7 @@ jobs:
working-directory: packages/shared-components
run: pnpm build:storybook
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: shared-components-storybook
path: packages/shared-components/storybook-static
@@ -20,7 +20,7 @@ jobs:
needs: build
environment: SharedComponents
steps:
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
name: shared-components-storybook
path: storybook-static
@@ -27,7 +27,7 @@ jobs:
run: "sudo apt-get install -y tree"
- name: Download Diffs
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
@@ -65,7 +65,7 @@ jobs:
- name: Upload received images & diffs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: received-images
path: packages/shared-components/__vis__/linux
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
sonarqube:
name: 🩻 SonarQube
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'merge_group'
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop # zizmor: ignore[unpinned-uses]
permissions:
actions: read
statuses: write
+16 -1
View File
@@ -49,6 +49,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
@@ -73,6 +75,19 @@ jobs:
run: git diff --exit-code
if: matrix.assert-diff
zizmor:
name: Zizmor Github Actions lint
runs-on: ubuntu-24.04
permissions:
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0
i18n:
strategy:
fail-fast: false
@@ -95,7 +110,7 @@ jobs:
- name: Shared Components
path: "packages/shared-components"
name: "i18n Check (${{ matrix.name }})"
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@abf186831e2acb3e98fca13a0711a3fa1364d2b1
permissions:
pull-requests: read
with:
+1 -1
View File
@@ -13,7 +13,7 @@ permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
sync-labels:
uses: element-hq/element-meta/.github/workflows/sync-labels.yml@develop
uses: element-hq/element-meta/.github/workflows/sync-labels.yml@dac99c67f08f8f2a079e885ffb682a2f39cd3960
with:
LABELS: |
element-hq/element-meta
+13 -7
View File
@@ -43,6 +43,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: pnpm cache
@@ -70,25 +71,29 @@ jobs:
working-directory: apps/web
run: |
pnpm test \
--coverage=${{ env.ENABLE_COVERAGE }} \
--coverage=$ENABLE_COVERAGE \
--ci \
--max-workers ${{ steps.cpu-cores.outputs.count }} \
--shard ${{ matrix.runner }}/${{ strategy.job-total }} \
--max-workers $MAX_WORKERS \
--shard "$SHARD" \
--cacheDirectory /tmp/jest_cache
env:
JEST_SONAR_UNIQUE_OUTPUT_NAME: true
# tell jest to use coloured output
FORCE_COLOR: true
MAX_WORKERS: ${{ steps.cpu-cores.outputs.count }}
SHARD: ${{ format('{0}/{1}', matrix.runner, strategy.job-total) }}
- name: Move coverage files into place
if: env.ENABLE_COVERAGE == 'true'
working-directory: apps/web
run: mv coverage/lcov.info coverage/${{ steps.setupNode.outputs.node-version }}-${{ matrix.runner }}.lcov.info
run: mv coverage/lcov.info coverage/$NODE_VERSION-${{ matrix.runner }}.lcov.info
env:
NODE_VERSION: ${{ steps.setupNode.outputs.node-version }}
- name: Upload Artifact
if: env.ENABLE_COVERAGE == 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: coverage-${{ matrix.runner }}
path: |
@@ -125,6 +130,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- name: pnpm cache
@@ -164,11 +170,11 @@ jobs:
- name: Run tests
working-directory: "packages/shared-components"
run: pnpm test:unit --coverage=${{ env.ENABLE_COVERAGE }}
run: pnpm test:unit --coverage=$ENABLE_COVERAGE
- name: Upload Artifact
if: env.ENABLE_COVERAGE == 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: coverage-sharedcomponents
path: |
+2
View File
@@ -10,6 +10,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
-3
View File
@@ -1,4 +1 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged --concurrent false
+1 -1
View File
@@ -1,3 +1,3 @@
{
"*": "prettier --write"
"*": "prettier --write --ignore-unknown"
}
+31
View File
@@ -1,3 +1,34 @@
Changes in [1.12.13](https://github.com/element-hq/element-web/releases/tag/v1.12.13) (2026-03-24)
==================================================================================================
## 🦖 Deprecations
* Remove automatic rageshakes on UTD labs feature ([#32778](https://github.com/element-hq/element-web/pull/32778)). Contributed by @Half-Shot.
* Remove automaticErrorReporting labs feature ([#32781](https://github.com/element-hq/element-web/pull/32781)). Contributed by @Half-Shot.
## ✨ Features
* Upgrade Element Call for new picture-in-picture designs ([#32816](https://github.com/element-hq/element-web/pull/32816)). Contributed by @robintown.
* Room list: add sections to shared components ([#32735](https://github.com/element-hq/element-web/pull/32735)). Contributed by @florianduros.
* feat: Devtool for sticky events MSC4354 ([#32741](https://github.com/element-hq/element-web/pull/32741)). Contributed by @BillCarsonFr.
* Port URL Preview components to MVVM ([#32525](https://github.com/element-hq/element-web/pull/32525)). Contributed by @Half-Shot.
* Add support for Widget \& Room Header Buttons module APIs ([#32734](https://github.com/element-hq/element-web/pull/32734)). Contributed by @dbkr.
* Port over linkifyJS to shared-components. ([#32731](https://github.com/element-hq/element-web/pull/32731)). Contributed by @Half-Shot.
* Redesign widget pip and move into shared component ([#32654](https://github.com/element-hq/element-web/pull/32654)). Contributed by @toger5.
* Implement customisations \& login component Module API 1.11.0 ([#32687](https://github.com/element-hq/element-web/pull/32687)). Contributed by @t3chguy.
* Realign MessageActionBar to Figma designs ([#32722](https://github.com/element-hq/element-web/pull/32722)). Contributed by @t3chguy.
* Implement new widget permissions module api ([#32565](https://github.com/element-hq/element-web/pull/32565)). Contributed by @langleyd.
## 🐛 Bug Fixes
* [Backport staging] Fix soft crash of room list when trying to open a room ([#32872](https://github.com/element-hq/element-web/pull/32872)). Contributed by @RiotRobot.
* Fix "key storage out of sync" appearing when key storage is actually fine ([#32811](https://github.com/element-hq/element-web/pull/32811)). Contributed by @andybalaam.
* Fix remove button styling in local address list of room settings ([#32798](https://github.com/element-hq/element-web/pull/32798)). Contributed by @florianduros.
* Always check \& update the users timezone on their profile on startup ([#32764](https://github.com/element-hq/element-web/pull/32764)). Contributed by @Half-Shot.
* Fix nx configuration to actually run type linter ([#32776](https://github.com/element-hq/element-web/pull/32776)). Contributed by @richvdh.
* Fix expand space panel button not being shown on keyboard focus ([#32746](https://github.com/element-hq/element-web/pull/32746)). Contributed by @t3chguy.
* Reset key storage if restoring from Recovery encounters the wrong decryption key ([#32668](https://github.com/element-hq/element-web/pull/32668)). Contributed by @andybalaam.
Changes in [1.12.12](https://github.com/element-hq/element-web/releases/tag/v1.12.12) (2026-03-10)
==================================================================================================
## ✨ Features
+3
View File
@@ -46,6 +46,9 @@ As for your PR description, it should include these things:
- Add comments to the diff for the reviewer that might help them to understand
why the change is necessary or how they might better understand and review it.
Please **_do not use force push_** in your PRs. Doing so means we can't see what
has changed. We use squash merge to get a "clean" git history.
### Changelogs
There's no need to manually add Changelog entries: we use information in the
+1 -1
View File
@@ -1,5 +1,5 @@
{
"*": "prettier --write",
"*": "prettier --write --ignore-unknown",
"src/**/*.(ts|tsx)": ["eslint --fix"],
"scripts/**/*.(ts|tsx)": ["eslint --fix"],
"module_system/**/*.(ts|tsx)": ["eslint --fix"],
+2 -2
View File
@@ -1,7 +1,7 @@
# syntax=docker.io/docker/dockerfile:1.21-labs@sha256:2e681d22e86e738a057075f930b81b2ab8bc2a34cd16001484a7453cfa7a03fb
# Builder
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:38edad6b2e5962120f5144ff9dd3dbd223c7f140ba6fa03920d62d28b021402b AS builder
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:d83f76ec9956d35fe14221022e8b5a87a5de6fcdb4edb1187eddeb7dc80cba71 AS builder
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
ARG USE_CUSTOM_SDKS=false
@@ -20,7 +20,7 @@ RUN /src/scripts/docker-package.sh
RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json
# App
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:c9448f9aaf2dee3dccfe0d2e51d6927cc9fbfdbcada66b0b01c0759816d86a5b
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:800307aaf07c143f5d90aa9d4269cc1971dcfe5aee7cabd11579ac4c6bcf198f
# Need root user to install packages & manipulate the usr directory
USER root
-1
View File
@@ -18,7 +18,6 @@
"https://scalar-staging.vector.im/api"
],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": false,
"room_directory": {
"servers": ["matrix.org", "gitter.im"]
-1
View File
@@ -18,7 +18,6 @@
"https://scalar-staging.vector.im/api"
],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": true,
"room_directory": {
"servers": ["matrix.org", "gitter.im"]
+3
View File
@@ -17,6 +17,9 @@ const config: Config = {
// This is needed to be able to load dual CJS/ESM WASM packages e.g. rust crypto & matrix-wywiwyg
customExportConditions: ["browser", "node"],
},
transform: {
"\\.[jt]sx?$": "babel-jest",
},
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)"],
globalSetup: "<rootDir>/test/globalSetup.ts",
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
+11 -15
View File
@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.12.12",
"version": "1.12.13",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -78,14 +78,10 @@
"jsrsasign": "^11.0.0",
"jszip": "^3.7.0",
"katex": "^0.16.0",
"linkify-html": "4.3.2",
"linkify-react": "4.3.2",
"linkify-string": "4.3.2",
"linkifyjs": "4.3.2",
"lodash": "npm:lodash-es@^4.17.21",
"maplibre-gl": "^5.0.0",
"matrix-encrypt-attachment": "^1.0.3",
"matrix-js-sdk": "41.1.0",
"matrix-js-sdk": "41.2.0",
"matrix-widget-api": "^1.16.1",
"memoize-one": "^6.0.0",
"mime": "^4.0.4",
@@ -93,7 +89,7 @@
"opus-recorder": "^8.0.3",
"pako": "^2.0.3",
"png-chunks-extract": "^1.0.0",
"posthog-js": "1.347.2",
"posthog-js": "1.356.1",
"qrcode": "1.5.4",
"re-resizable": "6.11.2",
"react": "catalog:",
@@ -105,7 +101,7 @@
"react-transition-group": "^4.4.1",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
"sanitize-html": "2.17.0",
"sanitize-html": "2.17.1",
"tar-js": "^0.3.0",
"ua-parser-js": "1.0.40",
"uuid": "^13.0.0",
@@ -129,9 +125,8 @@
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@babel/runtime": "^7.12.5",
"@casualbot/jest-sonar-reporter": "2.5.0",
"@element-hq/element-call-embedded": "0.16.3",
"@element-hq/element-call-embedded": "0.18.0",
"@element-hq/element-web-playwright-common": "catalog:",
"@element-hq/element-web-playwright-common-local": "workspace:*",
"@fetch-mock/jest": "^0.2.20",
@@ -139,7 +134,7 @@
"@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "catalog:",
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
"@sentry/webpack-plugin": "^4.0.0",
"@sentry/webpack-plugin": "^5.0.0",
"@stylistic/eslint-plugin": "^5.0.0",
"@svgr/webpack": "^8.0.0",
"@testing-library/dom": "^10.4.0",
@@ -179,14 +174,14 @@
"babel-loader": "^10.0.0",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"blob-polyfill": "^9.0.0",
"copy-webpack-plugin": "^13.0.0",
"copy-webpack-plugin": "^14.0.0",
"css-loader": "^7.0.0",
"css-minimizer-webpack-plugin": "^7.0.0",
"css-minimizer-webpack-plugin": "^8.0.0",
"dotenv": "^17.0.0",
"eslint": "8.57.1",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-deprecate": "0.8.7",
"eslint-plugin-deprecate": "0.9.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^29.0.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
@@ -210,6 +205,7 @@
"matrix-web-i18n": "catalog:",
"mini-css-extract-plugin": "2.10.0",
"modernizr": "^3.12.0",
"playwright-core": "catalog:",
"postcss": "8.5.6",
"postcss-easings": "4.0.0",
"postcss-hexrgba": "2.1.0",
@@ -250,6 +246,6 @@
"engines": {
"node": ">=22.18"
},
"packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
"packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
"private": true
}
@@ -245,9 +245,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
// Assert that replied audio file is rendered as file button inside ReplyChain
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']");
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody [role='button']");
// Assert that the file button has file name
await expect(button.locator(".mx_MFileBody_info_filename")).toBeVisible();
await expect(button.locator("span")).toBeVisible();
await takeSnapshots(page, app, "Selected EventTile of audio player with a reply");
},
@@ -307,9 +307,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
// Assert that the file button contains the name of the file sent at first
await expect(
replyChain
.locator(".mx_MFileBody_info[role='button']")
.locator(".mx_MFileBody_info_filename", { hasText: "upload-first.ogg" }),
replyChain.locator(".mx_MFileBody [role='button']").locator("span", { hasText: "upload-first.ogg" }),
).toBeVisible();
// Take snapshots
@@ -357,9 +355,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
const composer = thread.locator(".mx_MessageComposer--compact");
// Assert that the reply preview contains audio ReplyTile the file info button
await expect(
composer.locator(".mx_ReplyPreview .mx_ReplyTile .mx_MFileBody_info[role='button']"),
).toBeVisible();
await expect(composer.locator(".mx_ReplyPreview .mx_ReplyTile .mx_MFileBody [role='button']")).toBeVisible();
// Select :smile: emoji and send it
await composer.getByTestId("basicmessagecomposer").fill(":smile:");
@@ -367,6 +363,6 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await composer.getByTestId("basicmessagecomposer").press("Enter");
// Assert that the file name is rendered on the file button
await expect(threadTile.locator(".mx_ReplyTile .mx_MFileBody_info[role='button']")).toBeVisible();
await expect(threadTile.locator(".mx_ReplyTile .mx_MFileBody [role='button']")).toBeVisible();
});
});
+1 -1
View File
@@ -337,7 +337,7 @@ export async function disableKeyBackup(app: ElementAppPage): Promise<void> {
if (await keyStorageToggle.isChecked()) {
await encryptionTab.getByRole("switch", { name: "Allow key storage" }).click();
await encryptionTab.getByRole("button", { name: "Delete key storage" }).click();
await encryptionTab.getByRole("switch", { name: "Allow key storage" }).isVisible();
await expect(encryptionTab.getByRole("switch", { name: "Allow key storage" })).toBeVisible();
// Wait for the update to account data to stick
await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -35,6 +35,6 @@ test.describe("Devtools", () => {
await input.fill("https://example.com");
await input.press("Enter");
// expect EW NOT to reload
await page.getByText("Saved").isVisible();
await expect(page.getByText("Saved")).toBeVisible();
});
});
@@ -48,7 +48,7 @@ test.describe("Create Knock Room", () => {
await app.settings.openRoomSettings("Security & Privacy");
const settingsGroup = page.getByRole("group", { name: "Access" });
await expect(settingsGroup.getByRole("radio", { name: "Private (invite only)" })).toBeChecked();
await expect(settingsGroup.getByRole("radio", { name: "Invite only" })).toBeChecked();
await settingsGroup.getByText("Ask to join").click();
// Room should have a knock join rule
@@ -0,0 +1,58 @@
/*
Copyright 2026 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
test.describe("Message links", () => {
test.use({
displayName: "Alice",
room: async ({ user, app, bot }, use) => {
const roomId = await app.client.createRoom({ name: "Test room" });
await use({ roomId });
},
});
for (const link of ["https://example.org", "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
await app.client.sendMessage(room.roomId, `Check out ${link}`);
const linkElement = page.locator(".mx_EventTile_last").getByRole("link", { name: link });
await app.timeline.scrollToBottom();
await expect(linkElement).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
await app.client.sendMessage(room.roomId, `Check out @bob:example.org`);
const linkElement = page.locator(".mx_EventTile_last").getByRole("link", { name: "@bob:example.org" });
await expect(linkElement).toHaveAttribute("href", `https://matrix.to/#/@bob:example.org`);
});
test("should linkify a Room alias", 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 #aroom:example.org");
const linkElement = page.locator(".mx_EventTile_last").getByRole("link", { name: "#aroom:example.org" });
await expect(linkElement).toHaveAttribute("href", "https://matrix.to/#/#aroom:example.org");
});
test("should linkify text inside a URL preview", async ({ page, user, app, room }) => {
await page.route(/.*\/_matrix\/(client\/v1\/media|media\/v3)\/preview_url.*/, (route, request) => {
const requestedPage = new URL(request.url()).searchParams.get("url");
expect(requestedPage).toEqual("https://example.org/");
return route.fulfill({
json: {
"og:title": "A simple site",
"og:description": "And with a brief description containing https://example.org/another-link",
},
});
});
await page.goto(`#/room/${room.roomId}`);
await app.client.sendMessage(room.roomId, "Check out https://example.org/");
await expect(
page.locator(".mx_EventTile_last").getByRole("link", { name: "https://example.org/another-link" }),
).toBeVisible();
});
});
@@ -0,0 +1,48 @@
/*
Copyright 2026 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
test.describe("Topic links", () => {
test.use({
displayName: "Alice",
room: async ({ user, app, bot }, use) => {
const roomId = await app.client.createRoom({ name: "Test room" });
await use({ roomId });
},
});
for (const link of [
"https://example.org",
"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(
room.roomId,
"m.room.topic",
{
"m.topic": {
// Deliberately no HTML version.
"m.text": [
{
body: `An interesting room topic containing ${link}`,
},
],
},
"topic": `An interesting room topic containing ${link}`,
},
"",
);
await page.goto(`#/room/${room.roomId}`);
await expect(page.getByTestId("topic").getByRole("link", { name: link })).toBeVisible();
const locator = await app.toggleRoomInfoPanel();
await expect(locator.getByRole("link", { name: link })).toBeVisible();
});
}
});
@@ -85,7 +85,7 @@ test.describe("FilePanel", () => {
await expect(filePanelMessageList.locator(".mx_EventTile")).toHaveCount(3);
// Assert that the download links are rendered
await expect(filePanelMessageList.locator(".mx_MFileBody_download,.mx_MFileBody_info")).toHaveCount(3);
await expect(filePanelMessageList.locator(".mx_MFileBody")).toHaveCount(3);
// Assert that the sender of the files is rendered on all of the tiles
await expect(filePanelMessageList.getByText(NAME)).toHaveCount(3);
@@ -103,9 +103,7 @@ test.describe("FilePanel", () => {
// Detect the JSON file
// Assert that the tile is rendered as a button
const file = filePanelMessageList.locator(
".mx_EventTile_mediaLine .mx_MFileBody .mx_MFileBody_info[role='button'] .mx_MFileBody_info_filename",
);
const file = filePanelMessageList.locator(".mx_EventTile_mediaLine .mx_MFileBody [role='button']");
// Assert that the file name is rendered inside the button with ellipsis
await expect(file.getByText(/matrix.*?\.json/)).toBeVisible();
@@ -177,8 +175,7 @@ test.describe("FilePanel", () => {
const tile = page.locator(".mx_FilePanel .mx_EventTile");
// Assert that the file size is displayed in kibibytes, not kilobytes (1000 bytes)
// See: https://github.com/vector-im/element-web/issues/24866
await expect(tile.locator(".mx_MFileBody_info_filename", { hasText: size })).toBeVisible();
await expect(tile.locator(".mx_MFileBody_info", { hasText: size })).toBeVisible();
await expect(tile.locator(".mx_MFileBody [data-type='info']", { hasText: size })).toBeVisible();
});
});
@@ -194,7 +191,7 @@ test.describe("FilePanel", () => {
".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine.mx_EventTile_image .mx_MImageBody",
);
const link = imageBody.locator(".mx_MFileBody_download a");
const link = imageBody.locator(".mx_MFileBody a");
const downloadPromise = page.waitForEvent("download");
@@ -64,7 +64,7 @@ test.describe("Roles & Permissions room settings tab", () => {
const settingsGroupAccess = page.getByRole("group", { name: "Access" });
const settingsGroupHistory = page.getByRole("group", { name: "Who can read history?" });
await settingsGroupAccess.getByText("Public").click();
await settingsGroupAccess.getByText("Anyone", { exact: true }).click();
await settingsGroupHistory.getByText("Anyone").click();
// Test that we have the warning appear.
@@ -72,7 +72,7 @@ test.describe("Roles & Permissions room settings tab", () => {
await expect(axe).toHaveNoViolations();
await expect(settings).toMatchScreenshot("room-security-settings-world-readable.png");
await settingsGroupAccess.getByText("Private (invite only)").click();
await settingsGroupAccess.getByText("Invite only").click();
// Element should have automatically set the room to "sharing" history visibility
await expect(settingsGroupHistory.getByText("Members (full history)")).toBeChecked();
},
@@ -87,7 +87,7 @@ test.describe("Roles & Permissions room settings tab", () => {
const settingsGroupAccess = page.getByRole("group", { name: "Access" });
const settingsGroupHistory = page.getByRole("group", { name: "Who can read history?" });
await settingsGroupAccess.getByText("Public").click();
await settingsGroupAccess.getByText("Anyone", { exact: true }).click();
await settingsGroupHistory.getByText("Anyone").click();
// De-op ourselves
@@ -108,7 +108,7 @@ test.describe("Roles & Permissions room settings tab", () => {
await app.settings.switchTab("Security & Privacy");
await settingsGroupAccess.getByText("Private (invite only)").click();
await settingsGroupAccess.getByText("Invite only").click();
// Element should have automatically set the room to "sharing" history visibility
const errorDialog = page.getByRole("heading", { name: "Cannot make room private" });
await expect(errorDialog).toBeVisible();
@@ -384,7 +384,7 @@ test.describe("Spaces", () => {
});
await app.viewSpaceByName("My space");
await page.getByLabel("Settings", { exact: true }).click();
await app.settings.switchTab("Visibility");
await app.settings.switchTab("Security & Privacy");
axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view.
await expect(axe).toHaveNoViolations();
@@ -203,8 +203,6 @@ test.describe("Spotlight", () => {
.locator("..")
.locator("[role=menuitemradio]")
.click();
await page.waitForTimeout(3_600_000);
await page.waitForTimeout(500); // wait for the dialog to settle
const resultLocator = spotlight.results;
@@ -441,7 +441,7 @@ test.describe("Threads", () => {
textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
await textbox.fill("Hello Mr. User");
await textbox.press("Enter");
await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached();
await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeVisible();
// Close thread
await locator.getByTestId("base-card-close-button").click();
@@ -454,8 +454,8 @@ test.describe("Threads", () => {
await expect(page.locator(".mx_ThreadView_timelinePanelWrapper")).toHaveCount(1);
locator = page.locator(".mx_BaseCard");
await expect(locator.locator(".mx_EventTile").first().getByText("Hello Mr. Bot")).toBeAttached();
await expect(locator.locator(".mx_EventTile").last().getByText("Hello Mr. User")).toBeAttached();
await expect(locator.locator(".mx_EventTile").first().getByText("Hello Mr. Bot")).toBeVisible();
await expect(locator.locator(".mx_EventTile").last().getByText("Hello Mr. User")).toBeVisible();
});
test("navigate through right panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
@@ -0,0 +1,332 @@
/*
* 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 { type Page } from "playwright-core";
import { type StartedHomeserverContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
import { test, expect } from "../../element-web-test";
import { Bot } from "../../pages/bot";
import { type ElementAppPage } from "../../pages/ElementAppPage";
import { type Credentials } from "../../plugins/homeserver";
test.describe("Event List Summary", () => {
test.use({
displayName: "Finch",
});
test(
"should display a single join message on its own",
{ tag: "@screenshot" },
async ({ app, homeserver, page, user }) => {
const { bot, roomId } = await setupRoom(app, homeserver, page, user);
// When the bot joins the room
await bot.joinRoom(roomId);
// Then we say that in a generic event list summary
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "MyBot joined the room",
}),
).toBeVisible();
await replaceBotIds(page, bot);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("bot_joined_the_room.png", ignoreTimestamps);
},
);
test(
"should display a single ban message on its own",
{ tag: "@screenshot" },
async ({ app, homeserver, page, user }) => {
const { bot, roomId } = await setupRoom(app, homeserver, page, user);
// Given the bot is in the room
await bot.joinRoom(roomId);
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "MyBot joined the room",
}),
).toBeVisible();
// And we said something to separate out the messages
await app.client.sendMessage(roomId, "Saying something");
// When we ban the bot
await app.client.ban(roomId, bot.credentials.userId);
// Then we say that
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "banned",
}),
).toBeVisible();
await replaceBotIds(page, bot);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("bot_was_banned.png", ignoreTimestamps);
},
);
test(
"should display multiple join/leave messages as a group",
{ tag: "@screenshot" },
async ({ app, homeserver, page, user }) => {
const { bot, roomId } = await setupRoom(app, homeserver, page, user);
// Given the bot is in the room
await bot.joinRoom(roomId);
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "MyBot joined the room",
}),
).toBeVisible();
// When we perform multiple actions on it
await app.client.kick(roomId, bot.credentials.userId);
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
// Then those actions are gathered into a single summary
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "and joined",
}),
).toBeVisible();
await replaceBotIds(page, bot);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"multiple_join_leave_messages.png",
ignoreTimestamps,
);
},
);
test(
"should display multiple messages as a group",
{ tag: "@screenshot" },
async ({ app, homeserver, page, user }) => {
const { bot, roomId } = await setupRoom(app, homeserver, page, user);
// Given the bot is in the room
await bot.joinRoom(roomId);
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "MyBot joined the room",
}),
).toBeVisible();
// When we perform multiple actions on it, including a ban
await app.client.ban(roomId, bot.credentials.userId);
await app.client.unban(roomId, bot.credentials.userId);
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
// Then those actions are gathered into a single summary
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "and joined",
}),
).toBeVisible();
await replaceBotIds(page, bot);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"multiple_join_ban_messages.png",
ignoreTimestamps,
);
},
);
test(
"should display join/leave messages for multiple people as a group",
{ tag: "@screenshot" },
async ({ app, homeserver, page, user }) => {
const { bot, roomId } = await setupRoom(app, homeserver, page, user);
// Given the bot is in the room
const bot2 = new Bot(page, homeserver, {
displayName: "MyBot2",
autoAcceptInvites: false,
});
await bot2.prepareClient();
await app.client.inviteUser(roomId, bot2.credentials.userId);
await app.client.sendMessage(roomId, "I invited MyBot2...");
await bot.joinRoom(roomId);
await bot2.joinRoom(roomId);
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "MyBot2 joined the room",
}),
).toBeVisible();
// When we perform multiple actions on both bots
await app.client.kick(roomId, bot.credentials.userId);
await app.client.kick(roomId, bot2.credentials.userId);
await app.client.inviteUser(roomId, bot.credentials.userId);
await app.client.inviteUser(roomId, bot2.credentials.userId);
await bot.joinRoom(roomId);
await bot2.joinRoom(roomId);
// Then those actions are gathered into a single summary
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "joined, were removed, were invited, and joined",
}),
).toBeVisible();
await expect(page.locator('div[aria-label="3 members"]')).toBeVisible();
await replaceBotIds(page, bot, bot2);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"multiple_people_join_leave_messages.png",
ignoreTimestampsRightColumnAndHeader,
);
// And when we expand the summary
// Note: we can't include "expand" in the screenshot because it
// moves around, but at least we know it exists because we click it
// here.
await page.getByRole("button", { name: "expand" }).nth(3).click();
// Then we see all the individual actions
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "removed MyBot2",
}),
).toBeVisible();
await replaceBotIds(page, bot, bot2);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"multiple_people_join_leave_messages_expanded.png",
ignoreTimestampsRightColumnAndHeader,
);
},
);
test(
"should display join/ban messages for multiple people as a group",
{ tag: "@screenshot" },
async ({ app, homeserver, page, user }) => {
const { bot, roomId } = await setupRoom(app, homeserver, page, user);
// Given the bot is in the room
const bot2 = new Bot(page, homeserver, {
displayName: "MyBot2 with very long display name causing wrapping",
autoAcceptInvites: false,
});
await bot2.prepareClient();
await app.client.inviteUser(roomId, bot2.credentials.userId);
await app.client.sendMessage(roomId, "I invited MyBot2...");
await bot.joinRoom(roomId);
await bot2.joinRoom(roomId);
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "MyBot2 with very long display name causing wrapping joined the room",
}),
).toBeVisible();
// When we ban bot1 but not bot2
await app.client.ban(roomId, bot.credentials.userId);
await app.client.unban(roomId, bot.credentials.userId);
await app.client.kick(roomId, bot2.credentials.userId);
await app.client.inviteUser(roomId, bot.credentials.userId);
await app.client.inviteUser(roomId, bot2.credentials.userId);
await bot.joinRoom(roomId);
await bot2.joinRoom(roomId);
// Then those actions are gathered into a single summary
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "was removed, was invited, and joined",
}),
).toBeVisible();
await expect(page.locator('div[aria-label="3 members"]')).toBeVisible();
await replaceBotIds(page, bot, bot2);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"multiple_people_ban_messages.png",
ignoreTimestampsRightColumnAndHeader,
);
// And when we expand the summary
// Note: we can't include "expand" in the screenshot because it
// moves around, but at least we know it exists because we click it
// here.
await page.getByRole("button", { name: "expand" }).nth(3).click();
// Then we see all the individual actions
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: "removed MyBot2",
}),
).toBeVisible();
await replaceBotIds(page, bot, bot2);
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot(
"multiple_people_ban_messages_expanded.png",
ignoreTimestampsRightColumnAndHeader,
);
},
);
});
const ignoreTimestamps = {
css: ".mx_MessageTimestamp,.mx_TopUnreadMessagesBar { visibility: hidden; },",
};
const ignoreTimestampsRightColumnAndHeader = {
css:
".mx_MessageTimestamp," +
".mx_GenericEventListSummary_toggle," +
".mx_ReadReceiptGroup," +
".mx_RoomHeader, " +
".mx_TopUnreadMessagesBar " +
"{ visibility: hidden; }",
};
/**
* Create a room, join it, create a bot and invite it to the room.
*/
async function setupRoom(app: ElementAppPage, homeserver: StartedHomeserverContainer, page: Page, user: Credentials) {
const roomId = await app.client.createRoom({ name: "My room" });
await page.goto(`/#/room/${roomId}`);
await expect(
page.locator(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']", {
hasText: `${user.displayName} created and configured the room.`,
}),
).toBeVisible();
const bot = new Bot(page, homeserver, {
displayName: "MyBot",
autoAcceptInvites: false,
});
await bot.prepareClient();
await app.client.inviteUser(roomId, bot.credentials.userId);
await app.client.sendMessage(roomId, "I invited MyBot...");
return { bot, roomId };
}
/**
* Find the ID of the supplied bot in the page and replace it with a known string.
*
* This allows us to create consistent screenshots.
*/
async function replaceBotIds(page: Page, bot: Bot, bot2?: Bot) {
await page.evaluate(
([bot1UserId, bot2UserId]) => {
for (const el of document.querySelectorAll("div.mx_TextualEvent")) {
if ("innerText" in el) {
el.innerText = (el.innerText as any as string).replaceAll(bot1UserId, "<<replaced_bot1_id>>");
el.innerText = (el.innerText as any as string).replaceAll(bot2UserId, "<<replaced_bot2_id>>");
}
}
},
[bot.credentials.userId, bot2?.credentials?.userId ?? "no_bot_2_to_replace"],
);
}
@@ -779,7 +779,7 @@ test.describe("Timeline", () => {
// Assert that the file size is displayed in kibibytes (1024 bytes), not kilobytes (1000 bytes)
// See: https://github.com/vector-im/element-web/issues/24866
await expect(
page.locator(".mx_EventTile_last .mx_MFileBody_info_filename").getByText(/1.12 KB/),
page.locator(".mx_EventTile_last .mx_MFileBody [data-type='info']").getByText(/1.12 KB/),
).toBeVisible();
});
@@ -508,15 +508,16 @@ test.describe("Element Call", () => {
await openAndJoinCall(page);
await app.viewRoomByName("OtherRoom");
const pipContainer = page.locator(".mx_WidgetPip");
const pipContainer = page.getByTestId("widget-pip-container");
// We should have a PiP container here.
await expect(pipContainer).toBeVisible();
// Leave the call.
const overlay = page.locator(".mx_WidgetPip_overlay");
await overlay.hover({ timeout: 2000 }); // Show the call footer.
await overlay.getByRole("button", { name: "Leave", exact: true }).click();
const fakeWidget = page.locator('iframe[title="Element Call"]').contentFrame();
// await overlay.hover({ timeout: 2000 }); // Show the call footer.
await fakeWidget.getByRole("button", { name: "Close", exact: true }).click();
// PiP container goes.
await expect(pipContainer).not.toBeVisible();
@@ -541,15 +542,14 @@ test.describe("Element Call", () => {
await openAndJoinCall(page);
await app.viewRoomByName("OtherRoom");
const pipContainer = page.locator(".mx_WidgetPip");
const pipContainer = page.getByTestId("widget-pip-container");
// We should have a PiP container here.
await expect(pipContainer).toBeVisible();
// Leave the call.
const overlay = page.locator(".mx_WidgetPip_overlay");
await overlay.hover({ timeout: 2000 }); // Show the call footer.
await overlay.getByRole("button", { name: "Leave", exact: true }).click();
const fakeWidget = page.locator('iframe[title="Element Call"]').contentFrame();
await fakeWidget.getByRole("button", { name: "Close", exact: true }).click();
// PiP container goes.
await expect(pipContainer).not.toBeVisible();
@@ -578,15 +578,16 @@ test.describe("Element Call", () => {
await openAndJoinCall(page, true);
await app.viewRoomByName("OtherRoom");
const pipContainer = page.locator(".mx_WidgetPip");
const pipContainer = page.getByTestId("widget-pip-container");
// We should have a PiP container here.
await expect(pipContainer).toBeVisible();
// Leave the call.
const overlay = page.locator(".mx_WidgetPip_overlay");
await overlay.hover({ timeout: 2000 }); // Show the call footer.
await overlay.getByRole("button", { name: "Leave", exact: true }).click();
const fakeWidget = page.locator('iframe[title="Element Call"]').contentFrame();
// await overlay.hover({ timeout: 2000 }); // Show the call footer.
await fakeWidget.getByRole("button", { name: "Close", exact: true }).click();
// PiP container goes.
await expect(pipContainer).not.toBeVisible();
@@ -103,7 +103,7 @@ async function expectTimelineSticker(page: Page, serverName: string, roomId: str
}
async function expectFileTile(page: Page, roomId: string, contentUri: string) {
await expect(page.locator(".mx_MFileBody_info_filename")).toContainText(STICKER_NAME);
await expect(page.locator(".mx_MFileBody [data-type='info']")).toContainText(STICKER_NAME);
}
async function setWidgetAccountData(
@@ -139,7 +139,7 @@ test.describe("Widget PIP", () => {
);
// checks that pip window is opened
await expect(page.locator(".mx_WidgetPip")).toBeVisible();
await expect(page.getByTestId("widget-pip-container")).toBeVisible();
// checks that widget is opened in pip
const iframe = page.frameLocator(`iframe[title="${DEMO_WIDGET_NAME}"]`);
@@ -155,7 +155,7 @@ test.describe("Widget PIP", () => {
}
// checks that pip window is closed
await expect(iframe.locator(".mx_WidgetPip")).not.toBeVisible();
await expect(iframe.getByTestId("widget-pip-container")).not.toBeVisible();
});
}
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

+1 -1
View File
@@ -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:a28fb988827211b19f8983465a286291aa7112dfbe410ad24c849f74aee4ce0f";
"ghcr.io/element-hq/matrix-authentication-service:main@sha256:baa02c35e22dec0aad82b03de3157acd33b7b727a4b5fc68a67c512f0204684e";
/**
* MatrixAuthenticationServiceContainer which freezes the docker digest to
@@ -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:b935cb06b158966dad4faa81d021fc6c7c4080fa5361d9b3a08efc431a249a81";
"ghcr.io/element-hq/synapse:develop@sha256:b256d748144934a03434d57ca24659b1e432cea258bf6e41caaf4d84e498bd55";
/**
* SynapseContainer which freezes the docker digest to stabilise tests,
+6 -3
View File
@@ -34,9 +34,12 @@
"executor": "nx:run-commands",
"options": {
"commands": [
"tsc --noEmit --project ./tsconfig.module_system.json",
"tsc --noEmit",
"tsc --noEmit -p playwright"
// We indirect via `pnpm exec` to stop knip interpreting the
// commandline and declaring `playwright` and `./tsconfig.module_system.json`
// as unlisted dependencies.
"pnpm exec tsc --noEmit --project ./tsconfig.module_system.json",
"pnpm exec tsc --noEmit",
"pnpm exec tsc --noEmit --project playwright"
],
"parallel": false,
"cwd": "apps/web"
-4
View File
@@ -34,7 +34,6 @@
@import "./components/views/location/_ZoomButtons.pcss";
@import "./components/views/messages/_MBeaconBody.pcss";
@import "./components/views/messages/shared/_MediaProcessingError.pcss";
@import "./components/views/pips/_WidgetPip.pcss";
@import "./components/views/polls/_PollOption.pcss";
@import "./components/views/settings/_AddRemoveThreepids.pcss";
@import "./components/views/settings/devices/_CurrentDeviceSection.pcss";
@@ -237,7 +236,6 @@
@import "./views/messages/_MediaBody.pcss";
@import "./views/messages/_MessageActionBar.pcss";
@import "./views/messages/_MjolnirBody.pcss";
@import "./views/messages/_PinnedMessageBadge.pcss";
@import "./views/messages/_ReactionsRow.pcss";
@import "./views/messages/_RedactedBody.pcss";
@import "./views/messages/_RoomAvatarEvent.pcss";
@@ -278,8 +276,6 @@
@import "./views/rooms/_JumpToBottomButton.pcss";
@import "./views/rooms/_LegacyRoomList.pcss";
@import "./views/rooms/_LegacyRoomListHeader.pcss";
@import "./views/rooms/_LinkPreviewGroup.pcss";
@import "./views/rooms/_LinkPreviewWidget.pcss";
@import "./views/rooms/_LiveContentSummary.pcss";
@import "./views/rooms/_MemberListHeaderView.pcss";
@import "./views/rooms/_MemberListView.pcss";
@@ -1,73 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
$width: 320px;
$height: 220px;
.mx_WidgetPip {
width: $width;
height: $height;
}
.mx_WidgetPip_overlay {
width: $width;
height: $height;
position: absolute;
top: 0;
border-radius: 8px;
overflow: hidden;
color: $call-primary-content;
cursor: pointer;
}
.mx_WidgetPip_header,
.mx_WidgetPip_footer {
position: absolute;
left: 0;
height: 60px;
width: 100%;
box-sizing: border-box;
transition: opacity ease 0.15s;
}
.mx_WidgetPip_overlay:not(:hover) {
.mx_WidgetPip_header,
.mx_WidgetPip_footer {
opacity: 0;
}
}
.mx_WidgetPip_header {
top: 0;
padding: $spacing-12;
display: flex;
font-size: $font-12px;
font-weight: var(--cpd-font-weight-semibold);
background: linear-gradient(rgb(0, 0, 0, 0.9), rgb(0, 0, 0, 0));
}
.mx_WidgetPip_backButton {
height: $spacing-24;
display: flex;
align-items: center;
gap: $spacing-12;
> .mx_Icon {
color: $call-light-quaternary-content;
padding: 0;
}
}
.mx_WidgetPip_footer {
bottom: 0;
padding: $spacing-12 $spacing-8;
display: flex;
justify-content: flex-end;
align-items: flex-end;
background: linear-gradient(rgb(0, 0, 0, 0), rgb(0, 0, 0, 0.9));
}
-9
View File
@@ -1,9 +0,0 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound);
@import url("@vector-im/compound-web/dist/style.css");
+1 -1
View File
@@ -41,7 +41,7 @@ Please see LICENSE files in the repository root for full details.
padding-inline-start: 0;
}
.mx_MFileBody_download {
.mx_MFileBody [data-type="download"] {
margin-top: var(--cpd-space-4x);
}
+2 -1
View File
@@ -67,7 +67,8 @@ Please see LICENSE files in the repository root for full details.
}
}
&:hover .mx_SpacePanel_toggleCollapse {
&:hover .mx_SpacePanel_toggleCollapse,
.mx_SpacePanel_toggleCollapse:focus-visible {
opacity: 1;
/* For enhanced visibility under contrast control */
outline: 1px solid transparent;
@@ -187,3 +187,53 @@ Please see LICENSE files in the repository root for full details.
/* used on focus */
color: $links !important;
}
.mx_DevTools_sticky_explorer {
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
margin-top: var(--cpd-space-3x);
th {
text-align: left;
padding: var(--cpd-space-2x) var(--cpd-space-3x);
}
th#user_header {
width: 35%;
}
th#sticky_key_header {
width: 50%;
}
th#expires_in_header {
width: 15%;
}
tr {
cursor: pointer;
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-border-interactive-primary);
background: transparent;
}
tr:hover {
color: var(--cpd-color-text-secondary);
background: var(--cpd-color-bg-action-secondary-hovered);
}
tr:focus-visible {
outline: var(--cpd-border-width-2) solid var(--cpd-color-border-focused);
}
td {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: var(--cpd-space-2x) var(--cpd-space-3x);
}
td.remaining_time_column {
text-align: right;
}
}
}
@@ -41,7 +41,7 @@ Please see LICENSE files in the repository root for full details.
/* We also hide download links to not encourage users to try interacting */
.mx_EventTile_msgOption,
.mx_EventTile_e2eIcon,
.mx_MFileBody_download {
.mx_MFileBody [data-type="download"] {
display: none;
}
}
@@ -21,7 +21,6 @@ Please see LICENSE files in the repository root for full details.
vertical-align: middle;
width: 28px;
height: 28px;
background-color: $alert;
mask-size: 100%;
}
+14 -47
View File
@@ -6,56 +6,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
.mx_MFileBody_download {
.mx_MFileBody [data-type="download"] {
color: $accent;
height: var(--cpd-space-9x);
}
.mx_MFileBody_download object {
margin-left: -16px;
padding-right: 4px;
margin-top: -4px;
vertical-align: middle;
pointer-events: none;
}
/* Remove the border and padding for iframes for download links. */
.mx_MFileBody_download iframe {
margin: 0px;
padding: 0px;
border: none;
width: 100%;
}
.mx_MFileBody_info {
cursor: pointer;
.mx_MFileBody_info_icon {
background-color: $system;
border-radius: 20px;
display: inline-block;
width: 16px;
height: 16px;
padding: var(--cpd-space-2x);
vertical-align: middle;
margin-right: 12px;
svg {
color: $secondary-content;
width: inherit;
height: inherit;
display: block;
}
}
.mx_MFileBody_info_filename {
font: var(--cpd-font-body-md-regular);
color: var(--cpd-color-text-primary);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: inline-block;
width: calc(100% - 32px - 12px); /* 32px icon, 12px margin on the icon */
& object {
margin-left: -16px;
padding-right: 4px;
margin-top: -4px;
vertical-align: middle;
pointer-events: none;
}
}
.mx_MFileBody [data-type="info"] {
svg {
color: $secondary-content !important;
background-color: $system !important;
border-radius: 20px !important;
}
}
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
.mx_MessageActionBar {
--MessageActionBar-size-button: 28px;
--MessageActionBar-size-box: 32px; /* 28px + 2px (margin) * 2 */
--MessageActionBar-size-margin: 3px;
--MessageActionBar-item-hover-background: var(--cpd-color-bg-subtle-secondary);
--MessageActionBar-item-hover-borderRadius: 6px;
--MessageActionBar-item-hover-zIndex: 1;
@@ -17,12 +17,18 @@ Please see LICENSE files in the repository root for full details.
visibility: hidden;
cursor: pointer;
display: flex;
height: var(--MessageActionBar-size-box);
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: -32px;
top: calc(
-1 *
(
var(--MessageActionBar-size-button) + 2 *
(var(--MessageActionBar-size-margin) + var(--cpd-border-width-1))
)
);
right: 8px;
user-select: none;
/* Ensure the action bar appears above other things like the read marker */
@@ -74,7 +80,7 @@ Please see LICENSE files in the repository root for full details.
white-space: nowrap;
display: inline-block;
position: relative;
margin: 2px;
margin: var(--MessageActionBar-size-margin);
&:hover {
background: var(--MessageActionBar-item-hover-background);
@@ -84,7 +90,7 @@ Please see LICENSE files in the repository root for full details.
}
.mx_MessageActionBar_iconButton {
--MessageActionBar-icon-size: 18px;
--MessageActionBar-icon-size: 20px;
width: var(--MessageActionBar-size-button);
height: var(--MessageActionBar-size-button);
color: var(--cpd-color-icon-secondary);
@@ -108,30 +114,12 @@ Please see LICENSE files in the repository root for full details.
color: var(--cpd-color-icon-primary);
}
&.mx_MessageActionBar_threadButton {
--MessageActionBar-icon-size: 20px;
}
&.mx_MessageActionBar_retryButton {
--MessageActionBar-icon-size: 16px;
}
&.mx_MessageActionBar_downloadButton {
--MessageActionBar-icon-size: 20px;
&.mx_MessageActionBar_downloadSpinnerButton {
svg {
display: none; /* hide the download icon */
}
}
}
&.mx_MessageActionBar_expandCollapseMessageButton {
--MessageActionBar-icon-size: 12px;
}
}
.mx_MessageActionBar_optionsButton {
--MessageActionBar-icon-size: 22px;
}
}
@@ -9,29 +9,7 @@ Please see LICENSE files in the repository root for full details.
.mx_EventTileBubble.mx_cryptoEvent {
margin: var(--EventTileBubble_margin-block) auto;
&.mx_cryptoEvent_icon svg {
svg[data-state="supported"] {
color: $header-panel-text-primary-color;
}
.mx_cryptoEvent_state,
.mx_cryptoEvent_buttons {
grid-column: 3;
grid-row: 1 / 3;
}
.mx_cryptoEvent_buttons {
align-items: center;
display: flex;
gap: 5px;
}
.mx_cryptoEvent_state {
width: 130px;
padding: 10px 20px;
margin: auto 0;
text-align: center;
color: $tertiary-content;
overflow-wrap: break-word;
font-size: $font-12px;
}
}
+2 -10
View File
@@ -7,10 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
:root {
--AppTile_mini-height: 220px;
}
.mx_AppsDrawer {
--minWidth: 240px; /* TODO this should be 300px but that's too large */
@@ -168,11 +164,11 @@ Please see LICENSE files in the repository root for full details.
.mx_AppTile_mini {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
height: var(--AppTile_mini-height);
}
.mx_AppTile .mx_AppTile_persistedWrapper,
@@ -276,8 +272,8 @@ Please see LICENSE files in the repository root for full details.
&.mx_AppTileBody--large,
&.mx_AppTileBody--mini {
width: 100%;
height: 100%;
overflow: hidden;
height: var(--AppTileBody-height);
iframe {
border: none;
@@ -299,10 +295,6 @@ Please see LICENSE files in the repository root for full details.
}
}
&.mx_AppTileBody--mini {
--AppTileBody-height: var(--AppTile_mini-height);
}
&.mx_AppTileBody--loading {
display: flex;
flex-direction: column;
@@ -196,7 +196,9 @@ Please see LICENSE files in the repository root for full details.
}
.mx_MessageActionBar {
inset-inline-start: calc(100% - var(--MessageActionBar-size-box));
inset-inline-start: calc(
100% - var(--MessageActionBar-size-button) - 2 * var(--MessageActionBar-size-margin)
);
right: initial; /* Reset the default value */
}

Some files were not shown because too many files have changed in this diff Show More