diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index 33e1fc0..ab5456b 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -2188,9 +2188,16 @@ async function sfuPublishScreen(){
if (s.track.kind === 'video') setSenderMaxBitrate(s, 6000000);
if (s.track.kind === 'audio') setSenderMaxBitrate(s, 256000);
}
- watchPublishPC(pc, 'screen', sfuPublishScreen,
- () => sfuScreenPC === pc,
- () => { sfuScreenPC = null; sfuScreenPeerID = null; sfuScreenStream = null; });
+ /* Screen share CANNOT auto-rebuild like mic + camera — getDisplayMedia
+ * requires a fresh user gesture (transient activation). Auto-calling
+ * sfuPublishScreen() after a 'failed' state just fires the picker
+ * with no gesture and gets rejected. Instead: tear down on failed
+ * + surface the share button so the user can re-trigger themselves. */
+ pc.onconnectionstatechange = () => {
+ if (pc.connectionState !== 'failed' || sfuScreenPC !== pc) return;
+ logLine('err', 'sfu screen publish PC failed — tap "share screen" to re-share (browsers need a fresh click for screen capture)');
+ sfuUnpublishScreen().catch(()=>{});
+ };
logLine('', 'sfu: sharing screen as '+sfuScreenPeerID);
/* render a muted local preview so the publisher sees what they're
* sharing — SFU does not echo the publisher's own stream back */
@@ -4003,8 +4010,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');