diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index d662bf3..196e1c5 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -563,7 +563,11 @@ async function sfuSubscribe(){
/* server-initiated offer: POST /subscribe (empty body) — SFU answers with
* an SDP offer containing one m-line per current publisher. We answer it
* and POST the answer back, which completes the initial handshake. */
- const offerRes = await fetch(SFU_BASE + '/subscribe?room=' + encodeURIComponent(roomID), {
+ /* pass our pubkey as `sub` so the SFU can evict us if we're booted —
+ * server-side boot also calls SFU /internal/block which kicks any
+ * subscriber whose sub pubkey matches */
+ const subUrl = SFU_BASE + '/subscribe?room=' + encodeURIComponent(roomID) + '&sub=' + myKeys.pubHex;
+ const offerRes = await fetch(subUrl, {
method:'POST', headers:{'Content-Type':'application/json'}, body: '{}'
});
if (!offerRes.ok){ pc.close(); throw new Error('sfu subscribe http '+offerRes.status); }
@@ -914,10 +918,16 @@ async function handleSignal(raw){
if (mm) logLine('', mm.handle+' was removed by '+(by?by.handle:'a mod'));
if (m.uuid === myUUID){
/* server will close our socket; surface a clear notice and prevent
- * the WS reconnect loop from auto-rejoining into a boot loop */
+ * the WS reconnect loop from auto-rejoining into a boot loop. Tear
+ * down our SFU + mesh PCs too so we actually stop hearing /
+ * broadcasting — closing the WS alone leaves the WebRTC paths up. */
wantConnected = false;
showNotice('You were removed from this space'+(by?' by '+by.handle:'')+'.', 'warn');
logLine('err','you were removed from this space');
+ for (const u of [...peers.keys()]) tearPeer(u);
+ sfuUnpublish().catch(()=>{});
+ sfuUnsubscribe().catch(()=>{});
+ dropMic();
}
members.delete(m.uuid); tearPeer(m.uuid); handraise.delete(m.uuid);
renderRoom();
@@ -1288,8 +1298,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');