Add link previews, voice tiles and a menu volume slider; tidy the profile

- Link previews for web links in chat, fetched by the f0ckm upload host by
  default (linked sites never see the user's IP), or directly, or off
- Voice channel tiles above the chat on windows too narrow for the member list
- Per-person volume slider in the user context menu
- Profile panel: channel on its own line under the name, no Guest badge,
  voice actions moved to the context menu
- User information dialog refreshes every second
- Rate limiting mirrors Murmur's whole-second bucket leak, so irregular
  bursts are queued instead of dropped
- CHANGELOG.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 00:01:30 +02:00
co-authored by Claude Opus 5.5
parent 6d29ee1485
commit aa679dddcc
29 changed files with 541 additions and 60 deletions
+22
View File
@@ -139,3 +139,25 @@ test('voice packets are relayed through the TCP tunnel in the negotiated format'
}
});
test('irregular bursts match the server bucket (whole-second leaks)', { skip: !target }, async () => {
const alice = await connect('pace' + Date.now() % 1000);
const bob = await connect('paceB' + Date.now() % 1000);
try {
const got: string[] = [];
const total = 11;
const all = until<void>(res => bob.on('text', m => { got.push(m.html); if (got.length === total) res(); }), 25000);
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
// Gaps that do not line up with whole seconds used to make the client overestimate
const gaps = [0, 0, 0, 0, 0, 1900, 300, 300, 1400, 0, 0];
for (let i = 0; i < total; i++) {
await sleep(gaps[i]);
alice.sendText({ channels: [0] }, `p${i}`);
}
await all;
assert.deepEqual(got, gaps.map((_, i) => `p${i}`));
} finally {
alice.disconnect();
bob.disconnect();
}
});