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
61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
/*
|
|
Copyright 2026 Lotus Guild
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
MAX_REMEMBERED,
|
|
getRememberedVolume,
|
|
rememberVolume,
|
|
} from "./lotusVolumeMemory";
|
|
|
|
describe("lotusVolumeMemory", () => {
|
|
beforeEach(() => localStorage.clear());
|
|
|
|
it("defaults to 1 and remembers a set volume", () => {
|
|
expect(getRememberedVolume("@bob:x")).toBe(1);
|
|
rememberVolume("@bob:x", 0.4);
|
|
expect(getRememberedVolume("@bob:x")).toBe(0.4);
|
|
});
|
|
|
|
it("forgets the entry when set back to 1", () => {
|
|
rememberVolume("@bob:x", 0.4);
|
|
rememberVolume("@bob:x", 1);
|
|
expect(getRememberedVolume("@bob:x")).toBe(1);
|
|
expect(localStorage.getItem("lotus-per-user-volume")).toBe("{}");
|
|
});
|
|
|
|
it("keeps only the most recently set entries", () => {
|
|
for (let i = 0; i < MAX_REMEMBERED + 5; i++) rememberVolume(`@u${i}:x`, 0.5);
|
|
expect(getRememberedVolume("@u0:x")).toBe(1);
|
|
expect(getRememberedVolume("@u4:x")).toBe(1);
|
|
expect(getRememberedVolume("@u5:x")).toBe(0.5);
|
|
expect(getRememberedVolume(`@u${MAX_REMEMBERED + 4}:x`)).toBe(0.5);
|
|
});
|
|
|
|
it("re-setting an old entry makes it recent", () => {
|
|
for (let i = 0; i < MAX_REMEMBERED; i++) rememberVolume(`@u${i}:x`, 0.5);
|
|
rememberVolume("@u0:x", 0.7);
|
|
rememberVolume("@new:x", 0.3);
|
|
expect(getRememberedVolume("@u0:x")).toBe(0.7);
|
|
expect(getRememberedVolume("@u1:x")).toBe(1);
|
|
});
|
|
|
|
it("ignores junk in storage and invalid volumes", () => {
|
|
localStorage.setItem(
|
|
"lotus-per-user-volume",
|
|
JSON.stringify({ "@a:x": "loud", "@b:x": 0.2 }),
|
|
);
|
|
expect(getRememberedVolume("@a:x")).toBe(1);
|
|
expect(getRememberedVolume("@b:x")).toBe(0.2);
|
|
rememberVolume("@c:x", Number.NaN);
|
|
expect(getRememberedVolume("@c:x")).toBe(1);
|
|
localStorage.setItem("lotus-per-user-volume", "not json");
|
|
expect(getRememberedVolume("@b:x")).toBe(1);
|
|
});
|
|
});
|