Commit Graph
7598 Commits
Author SHA1 Message Date
fkwpandClaude Opus 5.5 993991105f Keep five backgrounds of their own, which fills the grid
- At four the grid stopped at eight tiles, since the add tile goes at the
  limit, and left its last corner empty. At five it is nine at the limit,
  and four with the add tile is nine one short of it.
- Guarded as the rule rather than the number: the grid is whole at the
  limit and one short of it. Fails at four.
- The stylesheet had claimed a whole three-by-three at the limit all along;
  now it is one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 13:09:17 +02:00
fkwpandClaude Opus 5.5 f2782c565e Add a background without putting it on, before joining too
- Added to the backgrounds on offer and in force once chosen, in the lobby
  exactly as in a call. Choosing a file and choosing to wear it are two
  decisions, and one rule for both places is one less thing to know.
- The footer's before-joining flag existed only for the other rule, so it
  goes, from both view models and the story.
- The edge-fade story waits longer for a scroll to settle: under the full
  parallel run a wait could outlast the default second.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 13:07:17 +02:00
fkwpandClaude Opus 5.5 fd972a717e Show that the camera menu scrolls, whatever the scrollbar does
- The platform fades its scrollbar out after a moment, and with a trackpad
  on a Mac there was then nothing to say the list scrolled: the backgrounds
  seemed simply to stop. Keeping the scrollbar would mean drawing our own,
  overriding a setting the user chose, and still not working in Firefox.
- Instead a fade at each edge of the list while there is more that way, as
  iOS, Android and Material do: the rows and tiles fade into it at the foot,
  and at the top it stands just below whichever heading is stuck there,
  where the content comes out from under it.
- Worked out from the list on scrolling, on resizing and after every render,
  since adding or removing a background changes its length without either.
- The camera menu only: in the microphone menu the level meter holds the
  foot of the list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 12:51:41 +02:00
fkwpandClaude Opus 5.5 b7ca586f78 Keep the headings over the grid, and the preview where there is no room
- The effect tiles are positioned, to carry their tick and the remove
  cross, and positioned content painted after a sticky heading slid over
  it: the grid ran across "Background effects" instead of under it. The
  headings now have a layer of their own.
- Where the call is too short to pin the preview, it is no longer dropped:
  it goes first into the list and scrolls away with the devices and the
  effects, seen on opening and out of the way once choosing.
- The list moves up over the menu's padding to bring it flush with the
  top, since a scrolling container cuts off what is pulled above it, and
  rounds its top corners with the frame's so a stuck heading cannot show
  past the curve.
- Guarded: the heading is what is on top where it stands, failing without
  the fix; and the preview sits inside the list and scrolls away.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 12:07:46 +02:00
fkwpandClaude Opus 5.5 efe6274636 Keep the menu's frame visible beside a scrolling list in Safari
- Once the device list had enough in it to scroll, Safari painted it above
  the outline the menu is framed with, and the frame vanished beside every
  device. Not new: the microphone menu with many devices lost 384px of it.
  The camera menu only met it because the self-preview shortens its list.
- The list now holds itself a border width clear of the frame, so nothing
  inside it has to: the headings and the meter lose their own clearance.
- Frame intact in both engines in every case measured. Belongs to the
  device menu, so it can be cherry-picked on its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 10:30:40 +02:00
fkwpandClaude Opus 5.5 8d95c76b8b Hold the camera menu at 296px, as design sets it
- A long device name now wraps instead of widening the menu over more of
  the picture the user is judging. Narrower only where the call area is,
  so it still fits inside it: 268px in a 300px call.
- The self-preview is always the sixteen by nine it is drawn at, 294 by
  166 inside the frame, and Safari's narrowed preview has nothing left to
  happen to.
- Tiles are thirds of the set width, about the 80px design draws, where
  their floor was there only to push the menu wider.
- Nothing else in the menu gets to set its width either: the picture
  sits over its box, and the notice and the refusal fit the menu rather
  than asking for their sentence on one line, which took it to 340px.
- The microphone menu is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 10:29:56 +02:00
fkwpandClaude Opus 5.5 31f057726f Fill the preview's width in Safari
- A ratio with a height cap let Safari keep the ratio by narrowing the box:
  once a long device name widened the menu past 313px, the preview stopped
  short and left a strip of menu at its right. Chromium stretched it.
- A fixed height instead, cropped by the picture: sixteen by nine at the
  menu's narrowest, wider as the menu widens, the same in both engines.
  Measured in WebKit before and after, 313px against a 398px menu, then
  398px.
- Guarded in a wide menu, though in Chromium only: no test tier here runs
  WebKit, so the story holds the requirement rather than the engine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 09:58:42 +02:00
fkwpandClaude Opus 5.5 45469f3828 Show how the user looks while they choose a background
- A live self-preview at the top of the camera menu, as design drew it:
  full width, flush with the top, sixteen by nine. Both sections scroll
  beneath it, because it is what the choosing below is for.
- Only where the call leaves room. It is paid for out of the device list's
  share rather than on top of it, and only where the list keeps its floor
  after paying, so the menu is never taller for having one.
- Attached to the track the pipeline is synced to, which both the lobby and
  the call already report, so it shows the effect in force without a capture
  or a pipeline of its own. Mirrored like the pre-join preview, a camera-off
  mark while there is no camera, and the wait while the first effect builds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 09:35:57 +02:00
fkwpandClaude Opus 5 8ffc8e2720 Sit the notice as far from the menu's foot as from its sides
- The menu carries bottom padding of its own, so setting one on the notice
  as well made the gap under it twice the gap beside it.
- Guarded as the relationship rather than the number, which is what anyone
  looking at it would notice. Fails at 32 against 16 without the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:04:40 +02:00
fkwpandClaude Opus 5 508fde1029 Say what the browser costs without raising its voice
- The design's second pass: the subtle ground, an outline mark and text at
  its ordinary weight, sixteen pixels in from the menu's sides like the
  grid above it. Measured against the drawing, the ground and the inset
  land on it exactly, and the padding gives the same box for the same
  number of lines.
- No longer an alert. An alert is for something that just happened; this
  was true before the menu opened and will be true after it closes, which
  is also why it cannot be dismissed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:59:25 +02:00
fkwpandClaude Opus 5 a2fc94a882 Guard that a shipped background can cover something
- Reads each shipped file and requires a format with no alpha channel in it.
  Fails on the overlay scrims that started this, which is the point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:10:26 +02:00
fkwpandClaude Opus 5 dfd42e2edd Give a background something to cover with
- The stand-in backgrounds were the app's own gradients, which are overlay
  scrims: measured, not one pixel in either is opaque, and where they look
  dark they are transparent. As a background their dark half was simply
  absent, which is what showed on the slow path.
- They are now laid on the canvas colour they are painted over and kept as
  JPEG, which cannot carry transparency at all.
- The same hole was waiting for anyone's own picture: a canvas starts
  transparent, WebP keeps an alpha channel, and a file small enough to keep
  as it was never passed through either. Every picture now goes through an
  opaque ground, whatever its size.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:59:07 +02:00
fkwpandClaude Opus 5 bfc42d8d7f Lay the background effect states out for review
- A docs page putting the four states side by side: offered, offered with
  what it costs, being set up, and not possible at all.
- The settling story plays the sequence rather than posing it, shortened so
  it is watchable.
- The notices carry the real strings, so a reviewer reads the words a user
  would rather than a paraphrase.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:49:40 +02:00
fkwpandClaude Opus 5 5b8e7d8cfd Offer effects wherever they run, and say what they cost
- 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>
2026-09-18 13:49:40 +02:00
fkwpandClaude Opus 5 2f214c8ca2 Ask the bench where the startup went
- Splits attaching from switching: building the segmenter fetches, compiles
  and sets up the GPU, while switching only tells a built one what to draw.
  One of those can be paid ahead of time.
- Reports the segmenter's assets as the browser saw them. A transfer size
  of zero means cache, which is what separates the first run on a device
  from every run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 11:52:31 +02:00
fkwpandClaude Opus 5 6a4bfa1b9a Put the bench's buckets on the clock
- They closed only when a frame arrived, so a freeze could not be reported
  as one: the iPhone's fourteen frozen seconds came back as a single
  bucket at a seventh of a frame per second.
- Adds the two numbers that say it plainly: time to the first frame, and
  the longest the picture simply stopped.
- A run now ends even if no further frame ever arrives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 11:49:07 +02:00
fkwpandClaude Opus 5 1e445a4d14 Measure whether a device can run background effects
- The camera and the pipeline, no call in the way: if a device cannot hold
  a frame rate here it cannot hold one in a call either.
- Reports mean and worst five-second frame rate, startup, and the worst
  stall on the main thread. Run no effect first; every number is only
  meaningful beside that device's own baseline.
- Ignores the desktop gate on purpose. Measuring what it forbids is the
  point, and the gate was written from reports rather than numbers.
- Exploration only, on an unlisted route, and one path segment deep: the
  config is fetched relative to the page, so a nested path never starts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 11:35:42 +02:00
fkwpandClaude Opus 5 aabd9a7022 Ask one question about whether effects can run
- The menu asked the browser for the APIs; the pipeline asked for those and
  a desktop. A phone browser sat between the two: every effect offered,
  none applied, nothing said.
- Both now ask the same function, so what is offered and what can be
  delivered cannot drift apart again.
- Tested against the lobby, where the switcher stays enabled on a phone —
  in a call it is already withheld there, so a check would pass whatever
  the verdict said. The test fails on the old wiring.
- This also fixes the blur toggle, which had the same split.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 11:14:44 +02:00
fkwpandClaude Opus 5 1299dad4b1 Draw the remove button as design drew it
- Critical from the moment it appears, not only under the pointer: the
  ground carries the hover instead, and gains a ring where the shadow was.
- 28px on the tile's corner, overhanging it by a fifth of itself, with the
  glyph a third of the button as the mock draws it.
- It names itself: a Remove tooltip, so the cross has words.
- Every colour is a Compound token and lands on the mock's own value in
  both themes, bar the dark hover ground, which is one step up the red
  ramp from what the mock measures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:58:13 +02:00
fkwpandClaude Opus 5 4cea64bd07 Format the branch
- oxfmt over the six files the exploration added or changed.
- Wrapping only, no behaviour: the formatter is a separate CI step from
  pnpm lint, which is why it went unnoticed on the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:24:12 +02:00
fkwpandClaude Opus 5 61cc12d937 Take the section rules from the device menu's new design
- The camera menu drew its own rule under each heading, because the menu
  suppressed Compound's. It no longer does, so ours drew a second one:
  the separator, its class and the heading override all go.
- The grid is its section's first control, so it starts under its rule
  where a device row's control starts under that one.
- Guarded in the story: two headings, each ruled edge to edge, nothing
  else dividing them, and both sections' first controls level.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:12:29 +02:00
fkwpandClaude Opus 5 c793cd5fc1 Open up the space between the effect tiles
- Measured off the design file: its gap is about a sixth of a tile's
  width, ours was under a tenth
- A step wider brings it to 14.3% against the design's 15.6%, the closest
  a spacing token reaches
- The tiles keep their width and the menu grows instead, which is the
  column floor doing its job; the grid still sits 16px from the frame
- Gives the remove cross on the corner room of its own, which is what
  prompted it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 8a2ad2226d Move the remove cross to the tile's corner
- On the top-right corner, where Zoom and Teams both put it
- Centred, it sat inside the target for choosing the tile: the one
  destructive action lived exactly where the ordinary one does, which is
  what was confusing people
- Red under the pointer. That alone would not have fixed it: colour says
  what a control does, it does not stop the press landing on the wrong one
- The one in force still offers no removal (FR-025). The corner would
  allow it, since cross and tick no longer compete for the middle, but
  that stays product's call

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 c7c44eea4e Put an added background on before joining, not during a call
- Before joining nobody sees the change, so the picture goes on at once
- In a call it waits to be chosen: otherwise choosing a file would change
  what everyone sees with no further word from the user
- Teams draws the line in the same place, which is what settled it: a
  background added before joining applies by itself there, one added in a
  meeting has to be applied
- The footer learns which side of joining it is on from its view model,
  since the lobby and the call build their own

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 ea25cde725 Add a background without putting it on
- Whether choosing a file should also wear it is still open, so this does
  the smaller thing: the image joins the others and the user says when
- Reverts the auto-selection added earlier, which was never proven: the
  stories carry selection as a fixed arg, so nothing there could show it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 31e67577b0 Keep the refusal out of the scrolling list
- It sat inside the device list, which scrolls and is bounded by the size
  of the call, so the menu's own height cut it off
- Now below that list, where nothing can clip it or leave it under the
  fold: no way to tell someone something failed
- 16px from the menu on every side, a step of which the menu's own column
  gap supplies

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 72ad732771 Space the refusal evenly, and let it go when the menu does
- It sat flush against the tiles with twice that gap beneath; now 16px
  all round, the same as everything else in the menu keeps from the frame
- It can be dismissed, and it does not outlive the menu it appeared in.
  Before, it stayed until the next attempt: closing the chevron and
  opening it again showed a message about a file chosen long ago
- Checked: shown on refusal, gone when dismissed, shown again on the next
  refusal, gone on reopening

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 fb223bcc4b Say why a file cannot be used as a background
- 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>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 3ac42a735b Make the cross remove by mouse, not just by keyboard
- The cross now sits beside the tile rather than inside it. Inside, the
  menu reached the item before any handler on the cross, whichever phase
  it was in: the tile was chosen, became the one in force, and the cross
  was taken away before it had acted
- That is why the keyboard worked and the mouse did not, and why the
  static stories missed it: with selection fixed by an arg, nothing
  re-rendered and the click survived
- A story with live selection covers it now: the cross removes the tile
  and does not make it the one in force on the way out

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 e3aaad8615 Let a user remove a background they added
- A cross where the tick would be, on hovering one of their own images
- Not offered on the one in force: that is what they are wearing, and
  taking it away would leave them with nothing chosen (FR-025)
- Delete or Backspace on a focused tile does the same, announced through
  aria-keyshortcuts. The cross is drawn rather than focusable: a control
  inside a menu item is invalid, and the item is what the arrows walk
- Covered by a story: the one in force offers no removal, the others say
  Delete and answer to it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 943cae0129 Sit the grid evenly in the menu, and the headings closer to their rules
- The grid now stands the same distance from the menu's frame on every
  side: 16px left, right, bottom, and above from its rule. It was 16 at
  the sides but 12 above and 28 below, since its own bottom padding and
  the menu's were both in play
- A ruled heading sits closer to its rule than the menu's own spacing
  allows, which is set for a heading with nothing under it: 14.5px from
  the heading's text rather than 18.5px, and the same in both sections
- Scoped to headings that are ruled off, so the microphone menu keeps the
  spacing it was drawn with

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 79eb5c8606 Draw the active frame at one width on every tile
- Thinner: colour alone, at the width every tile already carries
- The same on an added background as on no effect. It was thickened with
  an inset shadow, which draws inside the padding box where an image
  tile's picture sits, so the picture covered it and image tiles wore a
  thinner frame than the rest

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 35f9893b0a Even up the space below the two section rules
- The tile grid added its own room on top of the rule's, so the tiles sat
  20px below their rule while the device rows sat 12px below theirs
- The rule sets that distance now, and it is the same in both sections

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 304373e735 Rule off under each heading in the camera menu
- A rule under "Camera" as well as under "Background effects"
- Both run edge to edge: a separator insets itself by default, which
  leaves it floating short of the menu's sides
- The divider that separated the two groups goes; the rules do that work
- Closer to what they name than a separator's own spacing allows, which
  is set for one dividing two sections
- Scoped to the camera menu: the microphone menu still divides its two
  sections instead, which is the design the audio menu was drawn to

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 1af968748b Keep the menu open when adding a background, and choose it
- The picker moved into the menu: a native file dialog takes the focus,
  which the menu read as a click elsewhere and closed behind it
- Close requests are ignored while the picker is up, and a cancelled
  pick releases that through the input's own `cancel` event, which React
  does not type
- Adding answers with the background it kept, so the footer can choose
  it straight away rather than asking the user to pick it twice

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 81fe5d355f Let a user add a background of their own
- Added backgrounds are kept on the device in IndexedDB; nothing uploads
  them and no identifier would let anything fetch one
- Refuses what cannot be used and reduces what is larger than we keep:
  a 4000x3000 photo became 1920x1440 and 238KB became 5KB
- Animation is read from the file, not guessed from its type: an
  animated WebP and a still one share a type, and a .png may be an APNG
- Does not crop. The pipeline already covers the camera's frame with
  whatever it is given, recomputed as the frame changes, so cropping on
  the way in would bake in one shape and lose the rest for good
- The add tile is withheld once four are kept, rather than failing
- Added backgrounds live in their own context, not in ProcessorState:
  the publisher has no use for them and every stub of it would carry them

Not done: removing an added background, which the design gives no
affordance for, and telling the user why a file was refused, which has no
surface in the menu yet. Both are logged for now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 8a7eb5c7fd Make the settings blur control agree with the camera menu
- Both now read and write one setting, so they cannot disagree
- An image background reads as blur off there; turning blur on replaces
  the image, which is what FR-022 and EC-011 ask for
- The new setting defaults to whatever blur the user already had, so
  nobody loses the blur they turned on before this existed
- Covers the derivation with tests: what the checkbox shows, what it
  writes, and the fallback when a stored background is one we no longer
  ship

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 6a6605c233 Let background effects run under the dev server
- nodePolyfills rewrote MediaPipe's WASM loader, which mentions `process`
- That loader is a classic script whose job is to set self.ModuleFactory;
  as an ES module it sets nothing, so the segmenter threw
  "ModuleFactory not set" and no effect could run under `pnpm dev`
- Its transform now skips that one directory
- Affects blur on main too, not only this feature: neither could be
  exercised locally before
- Production was never affected, since the loader is emitted as an asset
  there; build still green and the assets still emitted

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 9179b1cccb Wire the background effects grid to the pipeline
- 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>
2026-09-17 23:01:54 +02:00
fkwpandClaude Opus 5 e823fbc85b Show background effects as a grid in the camera menu
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>
2026-09-17 23:01:53 +02:00
fkwpandClaude Opus 5 b90f267009 Attach background effects through one switchable pipeline
Replace the blur-only processor with a single wrapper that switches between no
effect, blur and a background image in place. Switching in place rather than
rebuilding matters: destroy() resets the transformer's first-frame flag, and
that flag makes it emit one unprocessed frame when it next starts processing,
so a rebuild per change would leak a frame of the real background every time.

Attach the pipeline the first time an effect is chosen and keep it attached
afterwards, including at no effect. Measured against a production build, the
segmentation assets are about 11.4MB and take ~143ms to initialise, so always
attaching would charge that to every user on every camera-on call, including
the majority who never choose an effect. Keeping it attached once it is there
costs about 25us per frame, which is within noise of no pipeline at all.

Complete init() while moving it. The old subclass replaced the base class's
init wholesale and dropped its image loading and disabled-mode setup, which
went unnoticed while blur was the only effect and blurRadius was the only
option that mattered.

Keep loading the segmenter from our own bundle. The library's assetPaths option
resolves WASM through MediaPipe's FilesetResolver from a directory, which is
not the same thing, and its defaults reach jsDelivr and googleapis.

Exploration for FEATURES_SPEC/2026-09_Background_Effects.md. Not for merge:
the two shipped backgrounds are stand-in gradients and there is no picker yet,
recorded as S1 and S2 in the spec.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:01:53 +02:00
fkwpandClaude Opus 5 5fff49670b Expect the default output entry to carry no name
- main stopped labelling the virtual default output with the first device's
  name (426acd22): a pinned sink is not re-routed, and which device the browser
  default resolves to is not knowable from here.
- The exploration still carried the older file, so this check passed locally
  and failed in CI, which tests the branch merged with main.
- AC8 is unchanged — Default is still a distinct entry beside the device it
  resolves to. Only the label it carries has moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:52:08 +02:00
fkwp 0633b24880 Merge remote-tracking branch 'origin/main' into fkwp/explore/quick-audio-menu 2026-09-17 22:50:39 +02:00
fkwpandClaude Opus 5 099950a100 Update the device menu to the latest design
- Each section is headed by its own rule, edge to edge, instead of one inset
  divider between the sections. Compound draws that rule under a menu heading
  already, so this removes our Separator, its spacing rule and its import.
- Our heading padding override goes too: Compound's menu heading is the design
  already — 13px/600 on a 19.5px line, #656D77, 16px either side, 7px from the
  text to the rule. Ours had been moving that 7px above the text.
- Two spacings do differ from Compound, both measured off the mock rather than
  guessed: 20px below the rule before a section's first device (27px rule to
  control, against Compound's 16px), and 28px above a heading that follows
  another section (41px from the last control to the next heading's text,
  against 22px). The first heading keeps Compound's spacing — the menu's own
  padding sits above it.
- Guarded by relationships, not numbers: the first control sits further below
  the rule than from the menu's edge, and a section stands further from the one
  above it than a heading does from its own first device. Both fail if either
  spacing goes back to Compound's 8px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:43:48 +02:00
Timo 03f81bcd64 Merge pull request #4266 from toger5/toger5/memberId-based-on-matrixRTCMode
Make the memberId dependent on the matrixRTC mode.
2026-09-17 14:02:32 +02:00
Timo K. 9f6c8bb434 Make the memberId dependent on the matrixRTC mode.
This outherwise creates to-device messages with the uuid for members
that use the userId:deviceId memberId in the state event.
2026-09-17 13:44:25 +02:00
fkwp 194bfea287 Cover the speaker section sitting above the microphone section
- Covers AC28, which `ux_design_shevron.png` settles.
- Not written in this session: it was uncommitted in the tree beforehand, and
  is committed here so it is not lost.
2026-09-17 11:56:31 +02:00
fkwp 4f8f71e861 Cover the device menu with stories and end-to-end specs
- The stories supply what the app supplies rather than what a harness has
  lying about: a microphone, and a call-sized root element.
- Invented device ids meant the menu asked for hardware that does not exist,
  so the meter reported no microphone. A tone played into a real MediaStream
  fixes it, and the meter runs its own analyser over it.
- Without a root element the list was bounded by Storybook's whole frame and
  the menu ran off the canvas; without a call-sized one, four devices scrolled.
- Standalone specs: the switch that does not drop the call, the meter alive
  while muted, the pinned meter, the focus ring.
- Component specs: the list sized to the call and not the window, resized
  while open, every device reachable, and the modality belonging to one call.
- A headless browser has one microphone and one speaker, so fake-devices.ts
  adds synthetic ones. It is explicit that it does not route audio.
2026-09-17 11:56:31 +02:00
fkwp d7e0444117 Write the mute button once instead of twice per kind
- Both arms of the switch were the same eleven lines of click, disabled, busy
  and enabled wiring; only the component and the test id differed.
- Two copies that had to be kept in sync by hand, for nothing.
- `toggles` becomes an expression rather than a `let` mutated in a switch,
  which makes the optional chaining downstream dead.
- The labels switch stays: i18n extraction needs literal `t("…")` keys, and it
  gives exhaustiveness checking on the union.
2026-09-17 11:56:31 +02:00
fkwp a6e00895a0 Track the focus modality on the menu itself
- Was watched at the document, so a key pressed in another Element Call on the
  same page — or in the host's own page — lit this menu's ring. Element Call
  can be mounted more than once, and the menu is portalled out of the root.
- Watched on the menu instead, which is the element that holds focus.
- A callback ref, not an effect on `menuOpen`: that state is ours and the open
  menu is Radix's, and they do not commit together, so the effect could run
  before there was anything to attach to.
- Recorded on the menu rather than in state, so every item it can focus answers
  to it — the camera menu's blur toggle kept the browser's own ring otherwise,
  which follows the pointer.
- Nothing re-renders when the modality changes now, so the reset folds in with
  the listeners and the second effect on that state goes.
- Comments moved to the code they describe; three unrelated ones had stacked up
  on one declaration.
2026-09-17 11:56:31 +02:00