Protocol 11: sit out replaces kicking; nudge

Anyone can sit anyone out (host included); only you bring yourself back.
Out members stay in chat and the party, never block or count, and load no
media; a card with the live position and Join back (Space) replaces the
video. The mini player's x sits you out; optionally idle does too. Nudge:
the server forwards it to the target only (3/s, 20/min per sender); the
target hears the nudge sound, gets a toast and a taskbar attention
request. Kick, its messages, errors and dialog are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-09 21:05:35 +03:00
1 parent dbfd351ee4
commit 8b81da4069
46 files changed
+855 -599

No files matched your search

-28
View File
@@ -21,22 +21,6 @@ void main() {
expect(s.gate(t), MediaGate.ok);
});
test('kick unknown throws, kick known forbids + drops', () async {
final s = Session(id: 'default', secret: 'shh');
expect(() => s.kick('nope'), throwsA(isA<AuthException>()));
final t = s.issueToken('mallory', '');
var dropped = false;
String? gotReason;
s.tokens[t]!.onKick = (reason) async {
dropped = true;
gotReason = reason;
};
await s.kick(t, reason: 'idle');
expect(s.gate(t), MediaGate.forbidden);
expect(dropped, isTrue);
expect(gotReason, 'idle');
});
test('oversize avatar rejected', () {
final s = Session(id: 'default', secret: 'shh');
final big = 'x' * (RelayConstants.maxAvatarBytes + 1);
@@ -122,18 +106,6 @@ void main() {
expect(() => WelcomeMessage.fromJson({'t': 'hello'}), throwsA(isA<ProtocolException>()));
});
test('kicked + kick request round-trip', () {
const k = KickedMessage(reason: 'idle');
final back = KickedMessage.fromJson(Map<String, Object?>.from(k.toJson()));
expect(back.reason, 'idle');
const r = KickRequest(target: 'abc', reason: 'afk?');
final backR = KickRequest.fromJson(Map<String, Object?>.from(r.toJson()));
expect(backR.target, 'abc');
expect(backR.reason, 'afk?');
expect(() => KickedMessage.fromJson({'t': 'err'}), throwsA(isA<ProtocolException>()));
expect(() => KickRequest.fromJson({'t': 'err'}), throwsA(isA<ProtocolException>()));
});
test('lastSeen touch + stale helper', () {
final s = Session(id: 'default', secret: 'shh');
final t = s.issueToken('alice', '');