my-sea voice: persist the call across in-sea reloads via auto-rejoin; pngquant the RWS card back — TDD

Voice-persistence follow-up (user-spec item 6). Every my-sea navigation is a
full page reload that kills the WebSocket + peer connections; true no-reload
nav would need an SPA refactor of the heavily-tested draw IIFEs. Instead we
auto-rejoin: bindVoiceBtn remembers the active room in sessionStorage on join
and silently re-joins it on the next my-sea page if voice is still available
there (mic permission persists for the session, so no prompt). Same user-
visible result (a brief reconnect, not seamless) with no risk to the draw flows.

- burger-btn.js: sessionStorage 'mysea-voice-room' remember/forget helpers +
  window.mySeaVoiceForget; bindVoiceBtn refactored to startCall()/withVoiceRoom()
  and auto-rejoins on bind when the remembered room === the active btn's room.
  A failed join (e.g. INSECURE_CONTEXT) forgets the room so it doesn't retry.
- _my_sea_gear.html: the NVM-disconnect guard confirm + BYE forget the room
  (and leave the mesh) — an explicit leave shouldn't auto-rejoin.
- BurgerSpec: +4 auto-rejoin specs (match / different-sea / inactive / remember
  + forget). 438 Jasmine specs green.

Also (bundled, user's parallel work): pngquant the resaved RWS deck card back
(tarot-rider-waite-smith-back.png) from 733KB truecolor+a to a 264KB 8-bit
palette PNG, matching its companion card faces. Dimensions preserved (the
rotated 401x694).

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-29 22:08:45 -04:00
parent 2cbc1bf292
commit c4e738ad16
5 changed files with 171 additions and 19 deletions

View File

@@ -249,3 +249,57 @@ describe("Burger", () => {
});
});
});
// Voice persistence across my_sea reloads — bindVoiceBtn remembers the active
// room in sessionStorage + silently re-joins it on the next page if voice is
// still available (2026-05-29).
describe("voice auto-rejoin", () => {
let vbtn, origVR, joinSpy;
beforeEach(() => {
try { sessionStorage.removeItem("mysea-voice-room"); } catch (e) {}
vbtn = document.createElement("button");
vbtn.id = "id_voice_btn";
vbtn.classList.add("active");
vbtn.setAttribute("data-room-id", "mysea-abc");
document.body.appendChild(vbtn);
origVR = window.VoiceRoom;
joinSpy = jasmine.createSpy("join").and.returnValue(Promise.resolve());
window.VoiceRoom = { join: joinSpy };
});
afterEach(() => {
window.VoiceRoom = origVR;
vbtn.remove();
try { sessionStorage.removeItem("mysea-voice-room"); } catch (e) {}
});
it("rejoins when the remembered room matches the active voice btn", () => {
sessionStorage.setItem("mysea-voice-room", "mysea-abc");
bindVoiceBtn();
expect(joinSpy).toHaveBeenCalledWith("mysea-abc");
});
it("does NOT rejoin when the remembered room is a different sea", () => {
sessionStorage.setItem("mysea-voice-room", "mysea-other");
bindVoiceBtn();
expect(joinSpy).not.toHaveBeenCalled();
});
it("does NOT rejoin when voice is unavailable (btn inactive)", () => {
vbtn.classList.remove("active");
sessionStorage.setItem("mysea-voice-room", "mysea-abc");
bindVoiceBtn();
expect(joinSpy).not.toHaveBeenCalled();
});
it("remembers the room on a manual join; mySeaVoiceForget clears it", () => {
bindVoiceBtn();
expect(joinSpy).not.toHaveBeenCalled(); // nothing remembered yet
vbtn.click();
expect(joinSpy).toHaveBeenCalledWith("mysea-abc");
expect(sessionStorage.getItem("mysea-voice-room")).toBe("mysea-abc");
window.mySeaVoiceForget();
expect(sessionStorage.getItem("mysea-voice-room")).toBeNull();
});
});