- Drops the desktop test. Measured, it sorted devices by the wrong thing:
a phone on the fast path held 99% of its frame rate and was refused, a
desktop on the slow one held 73% and was allowed.
- What the cost tracks is the processing path, and Safari and Firefox take
the slow one on every platform. That is told, not enforced: the cost is
smoothness, the reason to pay it is privacy, and only the user knows
whether they would rather show the room they are sitting in.
- The first effect of a session takes a while to prepare, so its own tile
spins where its tick will go, until a frame has actually come out of the
pipeline. The promises say nothing useful about when that is: attaching
resolves in about three seconds and switching in none at all, and the
slow path then holds the page still for twelve to fifteen more. The
spinner turns by rotating, which a browser composites, so it keeps moving
through a pause that has stopped everything else.
- Copy is the exploration's and needs an owner (S3).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- The reason appears with the grid, where the user chose the file, using
the same Alert the reaction menu already reports errors with
- A reason for each: not an image, animated, could not be opened, and a
fallback for anything the store itself refuses
- Named one by one rather than built from the reason, so the extractor
finds every string it has to translate
- The background in force is untouched, which is the other half of
FR-021: being refused a file changes nothing
- Covered by a story, and checked end to end by offering a text file:
the message appears, the menu stays open, nothing is kept
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Footer view model exposes the chosen effect and a setter for it
- The footer names the catalogue: a view model holds no translated text
- The camera menu shows the grid in place of the blur toggle
- Shipped images are numbered, not named, since the art is a stand-in
- Withholding the setter is what renders the section disabled, as with
the speaker section where no output can be chosen
Exploration for FEATURES_SPEC/2026-09_Background_Effects.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the Background effects section from the design: no effect, blur, the
shipped images and a tile for adding your own, under the camera list.
The tiles are menuitemradio like the device rows above rather than a new
control, so the menu keeps one keyboard model throughout and the selection is
announced rather than only drawn. An image tile is the picture, filling the
tile, and its name goes to assistive technology alone; the add tile is a plus,
named the same way. The tick stands where the glyph would, carrying its own
ground on a picture so it reads against whatever is behind it.
Repurposing a menu item as a tile means overriding its row layout on three
axes, each found by measuring rather than by eye: it lays itself out as a grid
of chevron, icon and label, so flex-direction does nothing; it carries the gap
between them as a right margin on each, which pushes both off the tile's
centre when they are stacked; and it insets its own text sixteen pixels, so
the grid needs the same inset to line its tiles up with the heading above them.
Sized against the design file rather than judged: glyph and tick a quarter of
the tile wide, an ink gap of about a seventh of its height to the label, and
the tile's proportion carried by its padding. Rows are equal height, set by
the tallest tile — a fixed ratio clipped the label as soon as the menu was
narrow enough that the ratio could not hold it — and the columns carry a
minimum width so the menu grows wide enough for three tiles rather than
shrinking them to whatever the device names need.
The blur hatch is a stand-in: the design system has no blur icon, and the
design's own is an icon whose strokes shorten towards the corners. A uniform
hatch fills its box, so this one is drawn smaller and finer to carry the same
weight rather than the same size.
Where background processing is unavailable, no effect stays choosable: it
needs none and it is what the user has. The rest are disabled, and the images
lose their colour as well as their contrast, since a washed-out photograph
still reads as one you could pick.
The existing blur toggle stays for callers that pass no effects, so nothing
that renders this component has to change yet.
Exploration for FEATURES_SPEC/2026-09_Background_Effects.md. Not for merge: the
image tiles are stand-in gradients drawn in the story, and nothing wires the
grid to the pipeline yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Draw the input level beneath the microphone list as 32 discrete segments,
so the level reads by count and not by hue alone.
- Expose it through role="meter" with aria-valuenow and aria-valuetext, so a
screen reader announces it.
- Give denied permission and no input device their own message and next
action, rather than a flat meter that reads as silence.
- Read the level only while the menu is open, so nothing holds a second
capture of the microphone for the length of a call.
- Read the device the picker has selected, so the meter re-points as soon as
the selection changes.
- Ignore anything below a noise floor: a quiet room is never digitally
silent, and its hiss otherwise lights the first segments permanently, which
reads as "it can hear me" when nobody is speaking.
- Quantise loudness with a square root above that floor: amplitude puts
ordinary speech near the bottom of the range, where a linear meter barely
moves.
- Withhold the output select callback where the platform cannot route audio
to a chosen device, which is what renders the speaker section disabled.
Safari and most Firefox builds land here.
- Track the in-flight device by kind as well as id: Chrome names both an
input and an output "default", so the spinner appeared on the wrong row.
- Disable every device in the menu while a selection is settling, so a second
request cannot overtake the first.
The meter opens its own short-lived capture rather than tapping the call's
audio track. That is what lets it follow the picker instantly, and it avoids
the pre-join track, which is deliberately frozen to the device selected when
the screen mounted. The cost is a second capture of the same device while the
menu is open. See the spec's notes sidecar.
Spec: FEATURES_SPEC/2026-09_Quick_Audio_Menu.md — AC1, AC6, AC7, AC13, AC14,
AC15, AC17, AC18, AC21, AC22
The microphone chevron listed input devices only, so changing the output
device meant leaving the call controls for the settings modal.
Render the audio menu as two sections, Speaker above Microphone, and wire
the output device list, selection and select callback through the footer
view model. Entries are shown but disabled where the platform cannot switch
that kind of device, or where only one exists, so the menu keeps the same
shape everywhere.
Spec: FEATURES_SPEC/2026-09_Quick_Audio_Menu.md — AC1, AC6, AC7, AC8
toggleScreenSharing only had `.catch(logger.error)`, so a getDisplayMedia
request that hangs (element-call-rageshakes#17152: Element Desktop on
Windows, the user pressed the screen share button 14 times in 25 seconds
and the log shows nothing but the toggle lines and livekit-client's
"waiting for pending publication promise timed out") left the user with
a button that does nothing and us with no evidence of why.
Log when a toggle is requested and when it completes or fails, with the
elapsed time, so a hang is visible in the logs. Explicit failures other
than the user cancelling the picker show as a non-modal "Could not start
screen sharing" toast. Nothing is inferred from a toggle taking a long
time: the user may simply be choosing what to share.
This is the mode in which we sent membership events with the 'oldest membership' transport selection algorithm, which stopped being the default back in version 0.21.0. Users will no longer be able to select this mode in developer settings, and admins will no longer be able to select legacy mode through the config either. The app will still continue to support *receiving* membership events with the 'oldest membership' transport selection algorithm from others, however.
Run `yarn i18n` to extract new translation keys for the media quality
settings, and update the DeveloperSettingsTab snapshot to include the
Camera quality, Screen sharing, and Audio processing sections.
once)
The local jwt token needs to be aquired via the right endpoint. The
endpoint defines how our rtcBackendIdentity is computed. Based on us
using sticky events or state events we also need to use the right
endpoint. This cannot be done generically in the connection manager. The
jwt token now is computed in the localTransport and the resolved sfu
config is passed to the connection manager.
Add JWT endpoint version and SFU config support Pin matrix-js-sdk to a
specific commit and update dev auth image tag. Propagate SFU config and
JWT endpoint choice through local transport, ConnectionManager and
Connection; add JwtEndpointVersion enum and LocalTransportWithSFUConfig
type. Add NO_MATRIX_2 auth error and locale string, thread
rtcBackendIdentity through UI props, and include related test, CSS and
minor imports updates
- Replace MatrixLivekitItem with MatrixLivekitMember, add displayName$
and participantId, and use explicit LiveKit participant types
- Make sessionBehaviors$ accept a props object and return a typed
RxRtcSession
- Update CallViewModel to use the new session behaviors, rebuild media
items from matrixLivekitMembers, handle missing connections and use
participantId-based keys
- Change localMembership/localTransport to accept Behavior-based
options, read options.value for enterRTCSession, and fix advertised
transport selection order
- Update tests and minor UI adjustments (settings modal livekitRooms
stubbed) and fix JSON formatting in locales