- The camera menu leads with the user's own camera, with the effect in
force, full width at sixteen by nine: a background is judged by how it
looks rather than by its thumbnail.
- It shows the pipeline's own track again, not a second capture. Each
provider keeps the camera its pipeline is synced to, before joining and
after, and hands it out with its state, so two calls on a page keep
their own. It is reported again only when it changes, which keeps the
preview from blanking on every change of state.
- It and the pre-join preview share useAttachedTrack, which detaches only
its own element: the pre-join preview detached every element showing
its track, the menu's among them.
- Paid for out of the list's share of the call, so a menu with it is no
taller: at the top where the list keeps its floor after paying, else
first among the options, scrolling away with them. Its size comes from
the tokens the stylesheets draw it with, so it is placed once.
- While the first effect is prepared the preview shows the wait; with the
camera off it says so, and an effect can still be chosen.
- The video is rounded itself as well as clipped by its frame: Firefox
composites it on a layer of its own, which the frame's rounded clip
didn't reach at 100% zoom. Confirmed by hand, as headless Firefox draws
it rounded either way.
- Every camera story shows the preview, with a stand-in camera drawn to a
stream. End to end on Chromium and Firefox, the lobby's menu shows the
camera and then the chosen picture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A cross on the tile's top-right corner removes an added background,
critical-coloured at rest and named by its tooltip, as design draws it.
The one in force offers none, since taking it away would leave nothing
chosen; the view model refuses it too.
- The cross sits beside the item rather than in it, which would take the
press first and choose the tile, and isn't a control of its own, so the
menu keeps one keyboard model: Delete or Backspace on the tile does the
same, and the tile says so with aria-keyshortcuts.
- Removed from IndexedDB and its picture released. End to end on
Chromium and Firefox, a background removed by Delete is still gone
after a reload.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Five of one's own at most, so the grid is whole: at the limit the add
tile goes, and it comes back once one is removed. The store holds the
limit too.
- An added image is refused if it isn't an image, is animated, or can't
be decoded; reduced to 1920px on its longest edge; and laid on an opaque
ground, since a transparent pixel would be a hole in the background.
- Animation is read from the file, as a still and an animated WebP share
a type. Chromium has no selected track until the decoder's tracks are
ready; without waiting for them an animated GIF got through.
- A refusal says why, in place of the notice, and is scrolled into view.
It can be dismissed, is gone once the menu closes, and the next shows
even if it reads the same. What was in force stays.
- End to end on Chromium and Firefox, from what the device then keeps: an
animated GIF is refused with its message, a 4000x3000 image is kept at
1920x1440, and a half transparent one is kept opaque. All three fail
with the preparation bypassed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- An add tile after the pictures opens the file picker, and the image
chosen joins the backgrounds on offer without being put on; it is in
force once chosen, like a shipped one. The tile is a command rather
than a choice, so a menu item beside the radios, and it isn't offered
where effects can't run.
- Kept in IndexedDB on this device and never sent anywhere, behind a
small interface, so the unit tests use an in-memory store; the
IndexedDB store itself is tested against fake-indexeddb, a new dev
dependency.
- The picker takes the focus, which the menu reads as a click elsewhere,
so the menu holds itself open until a file is chosen or the picker is
cancelled.
- A chosen image the device no longer keeps, as after its storage is
cleared, falls back to no effect once the device has said what it
keeps.
- Switching is checked between any two kinds of effect. End to end on
Chromium and Firefox: an added image shows in the preview once chosen;
once it is in force, no request carries the file or the copy kept,
which a deliberate upload fails; chosen, it is in force again after a
reload, gone once storage is cleared, and can be added again.
- Kept as given here; what can be added is checked in the commit after.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The first effect chosen in a session shows a spinner on its own tile,
in place of the tick, until a frame carrying it is drawn. Later changes
show nothing, having nothing to prepare.
- Ended by the frame, not the promises, which resolve while the segmenter
is still being built. The transformer reports the first frame that went
through the segmenter; one passed through untouched doesn't count.
- The pipeline publishes the wait with its state, and both footer view
models take it as an input.
- The lobby tests' provider mock gains the observable hook, built once: a
fresh observable per render rebuilt the footer on every render.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Where the browser has no MediaStreamTrackProcessor, effects draw each
frame through a canvas: slower, and the first build stalls the page.
They stay offered and choosable there, with a notice under the grid
that says so and describes the group.
- Decided in the view model, beside whether effects can run at all, so
the lobby and the call give the same answer.
- The notice stays within the menu's width however long the sentence,
which the story checks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The camera menu's blur toggle becomes a Background effects section
under the cameras: no effect, blur and the two shipped backgrounds as
one radio choice of tiles, three across. It scrolls with the device
list, so it stays reachable in a short call, and choosing keeps the
menu open. The lobby gets it too, as both render the same footer.
- The footer's view model gives the options, the notice and the actions;
the footer only names them.
- Each tile is Compound's MenuItem as a menuitemradio, styled as a tile.
Compound makes it an item of the dropdown, so the arrow keys walk the
tiles with the device rows, and takes its tap in the phone drawer. A
tile that can't be chosen has no select handler, so the keyboard passes
over it.
- Where the browser can't run effects the tiles are shown, disabled and
explained by a notice the group is described by. No effect stays
choosable and shows as in force. The notice is in the scrolling list,
so a short call doesn't push the menu out of it.
- Section headings are raised, or the positioned tiles scroll over the
one they sit under; raised within the list only, so the menu's frame
stays over them. Tiles clip with overflow: clip; with hidden they are
scroll containers, which focus scrolls into view without their border.
- End to end on Chromium and Firefox: a shipped background chosen in the
lobby shows in the preview, and None after Blur draws nothing, counted
in WebGL draws (Blur: about 1400 a second on Chromium, 2300 on
Firefox).
- The Quick Audio Menu's test of the camera menu keeps its name and now
finds blur among the effects rather than as a toggle.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Compound 10.3.0 bounds a menu by the space it has and scrolls what
doesn't fit, within a boundary the app passes. The menus pass the call
area, 16px in from its sides, rather than measuring its width.
- The 296px width is the menu's own style. Compound gives that style only
to the dropdown, so menuIsDrawer, which copied its phone rule, goes.
- Compound draws the menu's frame over its content, so nothing that
scrolls can hide it in Safari: the list, headings and meter no longer
keep a border width clear of it, and the checks that they did go.
- The device list is Compound's scroll area, so it fades out at the
foot with Compound's chevron while there is more below.
- The microphone menu's meter sticks at that foot, with rows scrolling
under it as before, so the list has no fade above it: Compound's is a
mask fixed to the list's edge, while a bounce at the top carries the
meter with the rows. The band under the meter is filled, or rows would
show there. The edge fades from where the chevron starts, so a bounce
fades the meter out before it reaches the chevron. A row reached by
keyboard stays above the meter.
- The list's height is still its share of the call.
- The component harness leaves the host's stylesheets unscoped, Compound's
among them, as a host does: scoped, they never reached the menu, which is
portalled out of Element Call's root.
- Its specs check only Element Call's own document styles on the host
page, and find the device list within Compound's scroll area.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The camera and microphone menus were as wide as their longest device
name, so a long one widened the menu over more of the call: 340px for a
long speaker name. Both are now 296px, as design sets them, and a long
name wraps.
- Narrower only where the call area is, so the menu still fits inside it:
268px in a 300px call. Taken from the observation of the call area that
already bounds the list's height.
- Held only for the dropdown. On Android and iOS Compound renders the menu
as a drawer as wide as the screen; menuIsDrawer follows its rule.
- Once the list is long enough to scroll, Safari paints it above the
outline the menu is framed with, and the frame vanished down both sides
of it: 384px of it on the many-devices microphone menu, in WebKit. The
list now keeps a border width clear of the frame, so the headings and
the meter inside it no longer need their own.
- Guarded with a long speaker name, which fails at 340px on main's sizing;
a unit test run as a phone, which fails with the width held there; and
the many-devices story's inset, which fails on the old stylesheet, since
the stories run in Chromium, which never showed the Safari bug.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Give the microphone level its own observable
* Add a microphone level meter
* Show speakers and microphones in the quick audio menu
* Wire speaker selection through the call footer
* Add stories for the meter and the device menu
* Add end-to-end specs for the quick audio menu
* Keep the footer while a menu opened from it is open
* Use the shared audio capture stub in the lobby test
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The view models reached for getUrlParams() — and so window.location —
from deep inside the call path: CallViewModel, MediaDevices, Publisher,
LocalMember and the footer view model. An embedded Element Call has no URL
of its own, so these values have to arrive as arguments instead.
Add the relevant options to CallViewModelOptions, to the MediaDevices and
Publisher constructors, to createLocalMembership$ and enterRTCSession, and
to createCallFooterViewModel. The remaining React consumers read the
context added in the previous commit. AppViewModel now takes its audio
output options too, moving that URL read out to main.tsx, where the app
shell can act as the adapter.
The new CallViewModelOptions fields are optional, defaulting to what the
URL parameters resolve to outside widget mode; the MediaDevices and
Publisher arguments are required, so that every construction site has to
be explicit.
useTheme.test.ts mocked the UrlParams module with a factory, so it needed
updating to mock the hook rather than getUrlParams.
No functional change.
* add MeidaMuteAndSwitchButton
* User button in footer
* Add tests
* update styling (dark bg on menu open + chevron white + chevron up)
* fix tests
* add storybook to CI
only add storybook with storybook label
test names
another env name test
TestName
new default name
remove label condition
Update pr-deploy.yaml
* Update pr-deploy.yaml
* add toggle example to default component
* hook up footer select actions
* fix video audio button (swapped) and lable in story