7523 Commits
Author SHA1 Message Date
Robin 2cac2d7e8b Merge pull request #4231 from element-hq/matthew/blur-processor-ended-track
Don't attach the background blur processor to an ended camera track
2026-09-10 21:27:11 +02:00
Robin 1695d3f518 Use flushPromises for readability 2026-09-10 21:15:48 +02:00
Robin 96a2e9b481 Merge pull request #4230 from element-hq/matthew/connection-stop-during-connect
Don't report an aborted connect as a connection error
2026-09-10 20:25:12 +02:00
Robin 23ba8f3204 Merge pull request #4249 from element-hq/matthew/audio-renderer-warn-spam
Stop MatrixAudioRenderer warning on every render
2026-09-10 19:56:56 +02:00
Matthew HodgsonandClaude Fable 5.1 b284bb00c0 Cover the stopProcessor failure path and trackProcessorSync
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSB8Qb89KDoTNRV83dWJHa
2026-09-10 18:28:57 +01:00
Johannes Marbach cb0ae89143 Merge pull request #4253 from element-hq/johannes/jwt-0.7.0
Bump lk-jwt-service to 0.7.0 in the docker setup and integrate it as an app service
2026-09-10 17:46:02 +02:00
Johannes Marbach 344aac21f9 Force undelegated mode in tests 2026-09-10 14:15:38 +02:00
Johannes Marbach 21d7f24f61 Bump lk-jwt-service to 0.7.0 in the docker setup and integrate it as an app service 2026-09-10 12:26:49 +02:00
Johannes Marbach 029622f07b Merge pull request #4251 from element-hq/renovate/npm-postcss-selector-parser-vulnerability
Update dependency postcss-selector-parser to v7.1.3 [SECURITY]
2026-09-10 07:42:07 +02:00
renovate[bot] 251535d371 Update dependency postcss-selector-parser to v7.1.3 [SECURITY] 2026-09-09 17:42:33 +00:00
Timo 5c6fa7c864 Merge pull request #4233 from element-hq/valere/component_ec_M1
ElementCall as a react component: refactor + a local dev harness
2026-09-09 19:39:30 +02:00
Timo K. be4023dd66 fix layout in spotlight (top inset) 2026-09-09 19:20:31 +02:00
Timo K. be5f3390f7 more tests 2026-09-09 17:01:16 +02:00
Timo K. 59ef5f5400 ci 2026-09-09 16:05:18 +02:00
Matthew Hodgson dbd2423e7b Stop MatrixAudioRenderer warning on every render
The "has no matching matrix call member" warning fired on every re-render
(i.e. every active speaker update) and also for the local participant's own
track, which is never rendered here anyway. Skip local tracks and warn once
per unexpected identity.
2026-09-09 11:20:49 +01:00
Timo K. 2ac95df75f update readmes 2026-09-09 12:15:19 +02:00
Timo K. 7821b465a5 Merge branch 'main' into valere/component_ec_M1 2026-09-09 10:23:47 +02:00
Robin b90ed5bdf7 Merge pull request #4247 from element-hq/fix-cd
Fix call.element.dev not updating after branch rename
2026-09-09 09:07:51 +02:00
Robin 1d7e2288f7 Fix call.element.dev not updating after branch rename 2026-09-09 08:49:08 +02:00
Robin 61eeb8171a Merge pull request #4242 from element-hq/delayed-leave-timings
Adapt delayed leave timings when delegation is available
2026-09-09 08:19:58 +02:00
Robin cff8766aa6 Delegate delayed leaves in LocalMember rather than LocalTransport
IMO this is where the delegation calls should have lived all along, since the leave event is part of the membership lifecycle, and we otherwise end up with an awkward hack to ignore transport updates.

Doing this now ensures that the client won't send any delegation requests if delegation is unsupported, and prepares the code for a future change in which we use the dedicated delegation endpoint from the CS API.
2026-09-08 21:22:52 +02:00
Robin 5cfe7ea4df Base JWT service endpoint choice directly on the MatrixRTC mode
Since we removed the extra 'legacy' mode, meaning there's nothing simplified anymore by translating things over to a JWT service version.

(Also, simplify some things by making the mode static for the duration of a call.)
2026-09-08 21:22:52 +02:00
Robin d38daef567 Reflect session config always being defined 2026-09-08 21:22:52 +02:00
Robin 09782a4d84 Give JWT token fetch procedures more appropriate names 2026-09-08 21:22:52 +02:00
Robin 839e36abd4 Make delegation support check fail immediately in case of CORS errors 2026-09-08 21:22:52 +02:00
Timo K. 3485ce47bc Merge branch 'main' into valere/component_ec_M1 2026-09-08 20:05:27 +02:00
Timo K. 273ee3b632 add allowJoinUnmutedViaIntent to the bridge
This allows us to control to never start unmuted in spa but be able to
start unmuted in widget and component mode.
2026-09-08 18:12:40 +02:00
Timo K.andClaude Fable 5.1 486ecbfda0 Stop offering media downloads on the component's host bridge
`downloadMedia` exists because a widget's client cannot fetch media: it
has no access token, and its only route to a file is MSC4039 over the
widget API. A component host hands over a client that does hold
credentials, so Element Call fetches media with it and the callback was
never needed there. It comes off `ElementCallHostBridge`, leaving a host
with only what it must understand, and stays on the internal HostBridge
for the widget.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 17:14:55 +02:00
Timo K.andClaude Fable 5.1 b722cc277e Make the theme a prop, next to the language
The theme is state — what Element Call should look like right now — and
so belongs beside `language` as a prop, not on the imperative handle
(where it was a request, `setTheme`, because the internal host bridge
speaks the widget API and a widget's host sends theme changes as
requests) and not in the configuration (where `config.theme` only ever
set the starting theme).

The `theme` prop feeds the same channel the rest of Element Call listens
to for a host's theme, replayed so that whatever subscribes after the
host has set it still hears the current one. Changing it re-themes the
container and nothing else; unlike the language, it is per component.
`setTheme` and `config.theme` are gone, and the harness gets a theme
picker in place of its per-pane buttons.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 17:02:24 +02:00
Timo K.andClaude Fable 5.1 f994586eeb Speak every language in the component, not just English
The component bundled English alone: the standalone app fetches its
locale files at runtime from URLs its own build emits, which a host
serving the library from elsewhere could not resolve, so bundling one
language was the self-contained option. Now every locale is a chunk of
its own that the host's bundler loads the first time it is needed, with
English still bundled in so that the fallback never waits.

Element Call starts in the browser's language and follows the host's own
setting through a `language` prop; `supportedLanguages` says what it
accepts. Translations are shared by every Element Call on the page, so
the most recently set language wins for all of them. The harness gets a
language picker, and the app and the component share the parsing of
locale paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:03:48 +02:00
Timo K.andClaude Fable 5.1 b7285064fd Let the shell say where home is, and drop the router from the call
The call reached for react-router in five places to send the user
"home": on leaving without a lobby, from the lobby's recents link, from
the post-call screen, from the error page's return button and from the
header logo. Home is the standalone app's home page; the call has no
idea where that is, and a component has no such place at all — its host
decides what follows a call. Yet the component had to mount a
MemoryRouter just so those hooks would not throw.

`useLeaveToHome` is the way home as the shell supplies it: the app
provides `navigate("/")` from inside its router, the component provides
nothing, and everything that used to link to "/" now either calls it or,
when there is none, offers no way out. The logo becomes a plain logo,
the recents and "not now" links disappear, the error page's button does
too. `ClientProvider`'s logout goes the same way. The component no
longer renders a router.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:39:00 +02:00
Timo K.andClaude Fable 5.1 f5a94ce702 Offer a component host only the configuration it can use
`ElementCallConfiguration` was every URL parameter, which put the widget
plumbing (widgetId, parentUrl, isWidget), the user's identity, the
homeserver, the analytics and Sentry settings, and the standalone app's
shared room secret in front of a host that has no business setting any
of them — the component reads none of those. It is now the behavioural
configuration a widget's URL can carry plus the two properties a host
has a say in: the theme and the background. The background is now
actually applied to the component's root, which it was not before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:39:00 +02:00
Timo K.andClaude Fable 5.1 3dc7cfe10b Say "component" for the component
"Embedded" already means something here: the embedded package is the
widget build. Where a comment meant Element Call running as a React
component inside a host application, it now says so, and the params it
starts from are `componentProperties`. "Host" stays the word for the
application on the other side, whether that is a widget container or an
application rendering the component.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:39:00 +02:00
Timo K.andClaude Fable 5.1 f70fba1dd6 Rename GroupCallView to CallView, and fold ElementCallView into it
Three layers stood between a shell and the call: `ElementCall` (the
component), `ElementCallView` and `GroupCallView`. The middle one only
held the `joined` flag and the mute state, both of which nothing outside
the call reads any more, so it is folded into the view it wrapped. That
view is now `CallView`, since "group call" is a name from before
Element Call handled anything else.

Its docstring says what it is: the whole lifecycle of a call — lobby,
the call itself, and what comes after — with the lobby and the post-call
screen each present or skipped depending on the parameters and the host.
Both the standalone RoomPage and the component render it directly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:38:59 +02:00
Timo K.andClaude Fable 5.1 8e8bc5ddde Talk to a component host in callbacks, not observables
The component exposed the internal HostBridge to hosts as-is, which
carried the host's requests as rxjs observables. That made rxjs part of
the public API of a package that bundles its own copy of it, so a host
would build bridges with a different rxjs than the one Element Call
consumed them with — and asked every host to learn rxjs to change the
theme.

A component host now implements plain async callbacks for what Element
Call tells it (`ElementCallHostBridge`, all optional), and makes its own
requests through an imperative handle on the component's `ref`
(`ElementCallHandle`: setTheme, join, hangUp, setDeviceMute), each
resolving once Element Call has acted and rejecting when nothing in
Element Call can. `component/host.ts` adapts that to the HostBridge the
rest of Element Call still speaks, with a bridge whose identity never
changes, so a host re-creating its callbacks on render restarts nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:38:59 +02:00
Timo K.andClaude Fable 5.1 d18e82c546 Catch the component's errors inside the component
Only the call body sat inside an error boundary; anything thrown in the
lobby, the mute state or the providers propagated into the host's React
tree, where React unmounts everything up to the nearest boundary — the
host's, or the host's whole application. The standalone app has a
boundary at its top; the component now has the same, showing the error
page inside its container and asking the host to stop keeping it on
screen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:38:59 +02:00
Timo K.andClaude Fable 5.1 779ee7a008 Finish measuring the root rather than the viewport
The move from media queries to `@container element-call` queries left a
few places still asking the viewport: the room header's compact size
and the lobby's footer placement went through `useMediaQuery`, the
lobby's video preview was `50vh` tall, the reaction picker was capped at
`100vw`, and the content insets and the gradient background were sized
from `100vw`/`100vh`. Embedded in a corner of a host's page, each of
those answered for the page rather than the corner.

`useRootSizeMatches` is `useMediaQuery` for the root element, built on
the same `observeElementSize$` the layout uses; the lengths become
container units. Container units resolve against the nearest query
container, and there are others in the tree (the spotlight layouts, the
media tiles), so base.css says when they may be used.

jsdom gives the body no size at all, which would have every such query
read as a tiny window; the test setup now gives it a desktop's, matching
what the media query mock already answered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:38:59 +02:00
Timo K.andClaude Fable 5.1 7cd21476f7 Say outright whether the profile is ours to change
Whether to offer the profile settings was inferred from whether the host
could close Element Call. For a component with no host bridge — the
default — nothing could, so an embedded Element Call let the user edit
the profile of an account that belongs to the host application.

`HostBridge.supportsProfileChanges` states it directly: true standalone,
where Element Call signed the user in itself; false for a widget's host
and for anything embedding the component (which sets it itself, since
the client it hands over is its own). The profile tab and the profile
shortcut follow that. What a host's ability to close us still decides —
what to show after the call ends — is a question about who owns our
lifetime, and stays keyed on `close`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:38:59 +02:00
Timo K.andClaude Fable 5.1 f0359259f9 Format base.css
The formatter wants the layered import of the Compound design tokens on
one line; "de-globalise styles" left it wrapped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:38:59 +02:00
Timo K.andClaude Fable 5.1 7137e6ec14 Update the developer settings snapshot for the scoped pre style
"de-globalise styles" gave the tab's <pre> elements a class instead of
styling the bare element, but did not update the snapshot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:56:10 +02:00
Timo K.andClaude Fable 5.1 6ae4f0539a Leave the page title to the page
The call and the lobby each set `document.title`, so a component
embedded in a host renamed the host's tab to "Element Call | <room>".
The title belongs to whoever owns the page: the standalone app's
RoomPage now sets it, for whichever room it has got as far as knowing
about, and the call itself no longer touches it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:53:50 +02:00
Timo K.andClaude Fable 5.1 bc58aed0d7 Keep keyboard shortcuts within Element Call's root
The call's shortcuts were listened for on the window and allowed
whenever focus was inside the standalone app's `#root` — which, for a
component embedded in a host, is the host's own root, or nothing. So m,
v and space fired while the user typed in the host's composer, and two
Element Calls on one page both answered every key.

Listen on the element Element Call treats as its root instead (the body
standalone, so nothing changes there), and judge whether a key press is
spoken for by what has focus — a dialog or a text field — rather than by
where it sits in the DOM, since the modals are now portalled to that
same root.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:52:34 +02:00
Timo K.andClaude Fable 5.1 608f107f8d End the component's media device scope on unmount
The component built its `MediaDevices` once, in a scope nothing ever
ended, so every mount left device observers running for the rest of the
page's life. Building it in an effect ties the scope to the component's
lifetime — and to the options it was built with, which were previously
frozen at first render.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:50:17 +02:00
Timo K.andClaude Fable 5.1 a010cc983f Compare the component's config by value
Everything downstream of the component's params — the mute state, the
call view model and with it the media connection — is keyed on the
identity of the params object, which was memoised on the identity of
the `config` prop. A host writing `config={{ ... }}` inline, which is
the natural way to write it, therefore tore the whole call down on
every render. The harness happened to pass a constant, so nothing
noticed.

`useStableValue` hands out the same object for as long as a deep
comparison says nothing changed, so an inline config costs nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:49:19 +02:00
Johannes Marbach 00d0adc3c4 Merge pull request #4222 from element-hq/renovate/all-minor-patch
Update all non-major dependencies
2026-09-08 11:40:20 +02:00
Johannes Marbach ddc5a50529 Disable new react compiler rules 2026-09-08 11:29:36 +02:00
renovate[bot] 27734a1a02 Update all non-major dependencies 2026-09-08 06:13:17 +00:00
Timo K. 489c1af426 test resizing via @container 2026-09-07 17:35:25 +02:00
Timo K. 365204d04c fix media queries in component based element call. 2026-09-07 14:50:36 +02:00
Johannes Marbach 14c61db43d Merge pull request #4241 from element-hq/matthew/encryption-mismatch-logging
Log track encryption flag and cryptor events for remote tracks
v0.26.0-rc.1
2026-09-07 13:30:50 +02:00