* 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>