getUserMedia is only exposed in a secure context (HTTPS, or the localhost
exemption). Reached over plain HTTP on a LAN IP — the dev server from a phone
at http://192.168.x.x:8000 — iOS/Android leave navigator.mediaDevices
undefined, so join() fetched TURN creds (a confusing 200) then silently
rejected deep in a .then() with no mic prompt. Desktop works because
127.0.0.1 IS a secure context. (Not a regression — voice never worked on the
HTTP dev server from mobile.)
- voice-mesh.js: _micSupported() seam + an early INSECURE_CONTEXT reject in
join() before any network, so the failure is fast + diagnosable.
- burger-btn.js: bindVoiceBtn catches the rejected join, rolls back the
optimistic .in-call/dataset.inCall (so the next click retries), and surfaces
a Brief — 'Voice needs HTTPS (or localhost) — your browser blocked the mic
here.' — instead of failing invisibly.
- VoiceMeshSpec: +2 specs (join rejects INSECURE_CONTEXT; btn rolls back +
Briefs on reject). Jasmine green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 4 of the my-sea voice batch (user-spec 2026-05-29).
── Voice-disconnect guard (item 6, the achievable slice) ──
Every my-sea navigation is a full page reload, which tears the WebRTC mesh
down — so voice can't literally persist across a reload without an SPA-style
no-reload nav (a separate, larger refactor, deferred). What ships now: the
gear-menu NVM warns before dropping the call.
- _my_sea_gear.html: the NVM routes through an inline, dependency-free
`mySeaGuardedNav(event, url)`. When voice is LIVE (VoiceRoom.localStream) AND
the target leaves my_sea (`url` has no `/my-sea`), it pops the shared guard
portal — "Leave the Sea? You'll disconnect from voice." — before navigating;
confirm proceeds, dismiss stays. NVMs that stay within my_sea, or any nav
with no live mic, go straight through. Covers all NVMs (owner my_sea, the
gatekeeper, the spectator) since they all include this partial.
── Bug B: desktop mute (mute robustness) ──
- voice-mesh.js: extracted `_applyMute()` and call it from join (post-
getUserMedia) as well as toggleMute. On desktop the first join pops a mic-
permission prompt; a mute toggled while that prompt is open used to be lost
because the stream didn't exist yet — re-applying after it resolves makes the
mute stick. Teardown resets `muted=false` so a rejoin starts clean.
- voice-glow.js: setVoiceState now syncs the voice btn's own flags
(.in-call / .muted / dataset.inCall) to the mesh truth, so a rejoin starts
clean and the glow's DOM fallback can't get stuck "live".
Tests: +5 VoiceMeshSpec mute specs (incl. the pre-stream-mute Bug-B case);
+2 NVM-guard FTs (warn when live / pass through when not); 3 gear-NVM ITs
updated for the mySeaGuardedNav markup. 286 gameboard ITs + 433 Jasmine specs
green.
Note: true cross-view voice persistence (no-reload within-my_sea nav) + the
desktop-mute live confirmation remain to verify on staging w. Redis up.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 3 of the my-sea voice batch (user-spec 2026-05-29). A --quaUser/--ninUser
glow + pulse machine for the burger btn + its voice sub-btn, driven by the
voice sub-btn availability (voice_active) + the live mesh state.
- voice-mesh.js: VoiceRoom gains a state-change hook — setOnStateChange(cb) +
peerCount() + _notify({inCall, peerCount, muted}), fired on join, every peer
add/drop, mute toggle, and teardown. No behaviour change without a subscriber
(VoiceMeshSpec stays green).
- voice-glow.js (new): the glow machine. PRE-JOIN nudge — burger glows when the
fan is closed (sea draw-nudge keeps burger precedence; voice reclaims it once
the sea glow clears), voice sub-btn glows when the fan opens. LIVE — the glow
PULSES on whichever surface shows (voice sub-btn fan-open, burger fan-closed):
base 2s cadence while alone, doubled (.voice-pulse--fast) once a 2nd party
connects (equalizer stand-in; a true volume-reactive equalizer is a live-only
enhancement). Class writes are reconciled (idempotent) so the burger-class
MutationObserver doesn't feed back on itself.
- _burger.scss: .voice-glow + @keyframes voice-pulse + .voice-pulse(--fast).
- loaded on my_sea.html + my_sea_visit.html (after burger-btn.js).
- VoiceGlowSpec.js (18 specs) + registered in SpecRunner; MySeaSeatsSpec flare
window updated 1.5s → 2s (Phase 2 bump). 428 Jasmine specs green.
Live-verify on staging: the actual glow colours/cadence + the equalizer
upgrade (item 5) and disconnect states (item 7) land in later phases.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>