* feat(web): add a success style to `EncryptionCard`
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(web): generate the recovery key inside the logout dialog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(web): cover generating the recovery key inside the logout dialog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: Group translations under settings|encryption|recovery
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Show read receipts in the new timeline
The labs timeline drew no read receipts. It now shows them, using the
same code as the old timeline, moved out of MessagePanel into shared
functions that both timelines call:
- getReadReceiptsForEvent and getReadReceiptsByShownEvent in
utils/read-receipts: which receipts an event carries (leaving out our
own and ignored users'), and which shown message each is drawn beside.
A receipt on a hidden event goes beside the last shown message before
it, a receipt that moves onto an event not loaded stays where it was,
and each message's receipts are ordered newest first.
- isSentState and findLastSuccessfulWeSent beside
isEligibleForSpecialReceipt: which message shows the "Sent" tick.
MessagePanel's behaviour is unchanged. RoomTimelineViewModel publishes
the receipts and the "Sent" message with every rebuild and whenever a
receipt arrives. NewTimelinePanel hands them to the tiles with one
shared receipt-position map, so avatars animate between rows as before,
and RoomView passes the room's showReadReceipts setting, which the panel
follows while the room is open.
Receipts and the "Sent" tick are worked out over the same events the
rows are built from, our pending messages included, so a message the
server has accepted takes the tick before its remote echo arrives, as
it does in the old timeline.
* Work out receipts and the "Sent" tick from one list of events per publish
republish built the loaded-plus-pending event list twice more, once for
the receipts and once for the "Sent" tick. Build it once and hand it to
both.
* Refresh read receipt avatars when room members change
Receipts carry the reader's room member for its avatar and name, but were
only updated when a receipt arrived or the rows were rebuilt. With
lazy-loaded members, receipts worked out before the members arrived kept a
placeholder avatar and a bare user id. The old timeline picked them up
through RoomView re-rendering on RoomStateEvent.Update; the view model
now listens for it too.
Receipt updates that arrive while a load or rebuild is due are skipped,
since the republish that follows updates the receipts for the new rows
anyway.
* Share the read receipt comparison and keep unchanged receipts
EventTile compared its readReceipts prop inline so a re-render that
moved no receipt did not redraw the tile. That check now lives in
utils/read-receipts as readReceiptsEqual (same readers in order, same
timestamps, same room members by reference), and also compares
timestamps, which the inline copy did not.
The new timeline's view model put the receipts in a fresh map on
every publish and every receipt, our own included, so every row was
redrawn even when no avatar moved. reuseUnchangedReadReceipts keeps the
previous list for each message whose receipts are unchanged, and the
previous map when none changed, so only the rows whose receipts moved
get new props.
* Replace delay options arg with numeric delay
When calling the (unstable) delayed event scheduling methods of
`MatrixClient`, pass a numeric delay instead of the now-deprecated delay
options object that contains the delay.
See https://github.com/matrix-org/matrix-js-sdk/pull/5199, which adds
support for a numeric delay & deprecates the delay options object.
* Update tests
* Expand the device wildcard when a widget sends an encrypted to-device message
A widget may address every device of a user with "*", which the server
expands for plaintext sends but the crypto layer can't; such sends
were dropped with "unknown device". Element Call subscribes to a
transcription bot this way, so captions waited forever.
* Expand the wildcard with an if, so coverage sees both branches
v8 reported the non-wildcard arm of the multi-line ternary as an
uncovered line even though both arms have tests, which failed diff-cover.
* Keep opening voice-only calls in PiP when the lobby is skipped
* Ignore Vitest's console-log teardown race in unit test runs
Vitest intermittently fails a run where every test passed with 'Closing rpc while "onUserConsoleLog" was pending'
(vitest-dev/vitest#11153).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Revert "Ignore Vitest's console-log teardown race in unit test runs"
This reverts commit 0c1084c6a4.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add custom lint rule to clean up timers before unmount
Claude wrote the linter plugin
* Add custom lint rule to clean up subscriptions before unmount
Claude wrote the linter plugin
Also cleaned up a stray listener in MPollBody calling `this.render.bind(this)` which would not do anything.
* oxfmt
Clicking one sent the user's scalar_token to a URL chosen by the message sender.
Remove the action body wrapper from TextualBodyView
It existed only for NEB starter links, which are no longer supported.
Rename ScalarAuthClient.getStarterLink to appendScalarToken
Clicking one sent the user's scalar_token to a URL chosen by the message sender.
Remove the action body wrapper from TextualBodyView
It existed only for NEB starter links, which are no longer supported.
Rename ScalarAuthClient.getStarterLink to appendScalarToken
* Fix /spoiler command not converting message content to HTML
The spoiler slash command was inserting the raw message text directly into
the formatted_body HTML span, so markdown syntax and special characters
were never processed. Run the message through htmlSerializeFromMdIfNeeded
so that e.g. **bold** becomes <strong>bold</strong> inside the spoiler span.
Fixes#21185
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* tests: add vitest tests for /spoiler markdown serialization fix
* fix(/spoiler): reject empty message and fix test assertions
Two issues:
1. The runFn used a default `message = ""` so /spoiler with no args
silently sent an empty spoiler. Added a guard that returns
reject(this.getUsage()) when message is empty/whitespace.
2. The tests accessed result.value which does not exist on RunResult.
RunResult uses result.promise (a Promise). Updated all three
success-path assertions to await result.promise.
* fix(test): cast IContent to fix formatted_body TypeScript error in spoiler test
Property 'formatted_body' does not exist on RoomMessageEventContent — cast
the awaited promise result to IContent which declares formatted_body as an
optional string field.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Address review: assert full formatted_body snapshot and explain forceHTML
* Use exact toEqual assertions in spoiler tests instead of snapshots
Snapshots require local generation before CI can use them.
toEqual with the precise expected strings is clearer and does not
need a separate generation step.
* Fix copyright holder in spoiler test to external contributor name
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Florian Duros <florianduros@element.io>
* Update "your chats are backed up" copy
... in the Settings dialog (RecoveryPanel), and the "set up recovery" toast.
* Change labels on "set up recovery" UI
* Add help text to 'Allow key storage' toggle
* update playwright snapshots
The header's Join button for a voice call went through the voice-call
PiP shortcut, so (outside DMs, where Element Call skips the lobby) the
user landed in a lobby inside a PiP. Only starting a voice call should
open in PiP; joining one in progress opens it maximised like video.
* Avoid unhandled promise rejection from CallStore.onReady()
As well as the usual network/server errors, a call to `waitForClientWellKnown()` can throw whilst the client is still starting up.
* Appease the CI
---------
Co-authored-by: Johannes Marbach <johannesm@element.io>
Co-authored-by: Johannes Marbach <n0-0ne+github@mailbox.org>
* refactor(web): split `ChangeRecoveryKeyBody` out of the settings breadcrumbs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(web): cover `ChangeRecoveryKeyBody` outside settings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: Update deprecation notice on `CreateSecretStorageDialog`
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(web): match logout confirmation copy to new design
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
* refactor(web): let `LogoutDialog` open any encryption settings state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(web): warn about recovery key when removing the only device
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
* test(web): cover recovery key warning when removing the only device
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
* fixup! refactor(web): let `LogoutDialog` open any encryption settings state
---------
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Extend the module api hints with renderAsInformationalMessage and renderSenderProfile
* Use the new render hints in EventTiles and MainGrouper
* Update tests
* Use datatestid tags in playwritgh tests
* Fix playwright tests
* Better documentation and updated test
* Add renderGroupSummary to module api
* Merge custom group summaries with existing
* Add tests for custom group summaries
* Adjust custom renderer tests
* Reuse custom renderer metadata during timeline rendering
* Defer event-list summary generation until collapse
* Updated tests
* renderAsInformationalMessage to renderAsInfoMessage
* Fix sonar issues
* Added spy tracking and restore
* oxfmt
* Added TSDoc
The "read up to" marker only searched the room's main timeline for the
event. A thread reply lives in its thread's timeline, so it was never
found there and taken as already seen by the widget. Look in the thread's
timeline instead, with a marker per thread.
* Allow using Element Call with URL-only LiveKit transports
* Rephrase test
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
* Rephrase test
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
* Fix blank maps by bundling the maplibre-gl worker explicitly
maplibre-gl v6 locates its tile-parsing worker from `import.meta.url`,
which webpack inlines as a file:// path. maplibre rejects that, so no
worker is spawned and maps render only their background with no error.
Build the worker as its own entry and point maplibre at it.
* Resolve the maplibre worker inside the hashed bundle directory
The webpack public path is the webapp root, not the bundle directory,
so the worker URL pointed at a file that does not exist and maplibre
hung silently. Ask webpack for the entry's full filename instead.
* Test that maplibre is pointed at the bundled worker
(cherry picked from commit efca432abd)
Co-authored-by: Matthew Hodgson <matthew@matrix.org>
* Fix blank maps by bundling the maplibre-gl worker explicitly
maplibre-gl v6 locates its tile-parsing worker from `import.meta.url`,
which webpack inlines as a file:// path. maplibre rejects that, so no
worker is spawned and maps render only their background with no error.
Build the worker as its own entry and point maplibre at it.
* Resolve the maplibre worker inside the hashed bundle directory
The webpack public path is the webapp root, not the bundle directory,
so the worker URL pointed at a file that does not exist and maplibre
hung silently. Ask webpack for the entry's full filename instead.
* Test that maplibre is pointed at the bundled worker
* Reapply not allowing skip device verification by default.
Reverts the revert commit 417fa60b0e.
* Switch to a new local store entry to check if verification is enforced, that is only set when the force_verification is true at the login.
* Test that force_verification does not affect existing sessions.
* Update test title
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
* Fix formatting
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
* Fix test title
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
* Address review comments for 'should not force verification on reload if force_verification is enabled after login' test.
---------
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
* Update compound-web to 10.3.1
To fix tooltip overflow bug
* Fix success to accent
* snapshot/test updates
* Gah the snapshots
* more snapshots
* Last lot of snapshots, hopefully
* Change the brittle CSS override to reflect the new CSS
* Same hack on date picker context menu
* s/success/accent/ and another padding override
* Update for really tiny font rendering differences