The per-participant volume slider lived only in the tile's view model, so it
reset to 100 % on the next call, a reload, or when that person reconnected.
- lotus/lotusVolumeMemory.ts: per-Matrix-user volume in localStorage
(`lotus-per-user-volume`), keyed by user id so it follows them across
devices; 1 (100 %) forgets the entry; at most 50 people, least recently set
dropped first; junk in storage ignored. Local only, never synced.
- VolumeControls take an optional initialVolume and onCommit; remote user
media seeds from and saves to the memory. Screenshare audio stays per-share.
- commitPlaybackVolume now takes the slider's committed value. With the
keyboard the slider commits before its last change arrives, so the
committed (and remembered) volume was one step behind, and returning to
100 % with End never registered.
Verified in a two-person local call: Alice sets Bob to 70 % with the
keyboard → audio element 0.70, stored 0.7; leave + rejoin → slider and audio
0.70; End → entry removed. Unit tests: memory module (LRU, reset, junk),
remembered volume restore + commit-with-value; state/tile/room/lotus suites
all pass on Node 22 (410 tests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA