feat: close-to-tray, meeting lobby, speaker select, link expiry, mobile RC touch (0.1.14/batch72)

General #1/#2 (closed-app notifications): the desktop app now CLOSES TO TRAY instead
of quitting, keeping its chat WebSocket alive so calls/messages still notify. Tray
icon + menu (Open / Quit), single-instance lock, first-close hint.

Guest #4 (lobby/admit): meetings can require the host to admit guests joining by link.
Setting on the schedule form ("Guests must be admitted by the host", default on) +
ad-hoc default. Guests wait on a "waiting to be let in" screen; the host gets an
Admit/Deny prompt; auto-cleanup on leave. Logged-in members always join directly.

Guest #5 (speaker): headphones/speaker output picker in the meeting (setSinkId),
remembered and applied to every tile.

Guest #3 (link expiry): guest link/token dies ~2h after a scheduled meeting's end
(HTTP 410) with a clear message; live-room links expire when the room empties.

RC #4 (mobile): touch→mouse mapping so a phone/tablet viewer can control (tap=click,
drag=move). Uses the same letterbox-correct coordinate mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 14:44:58 +05:30
parent bd488b3286
commit d0f9355553
9 changed files with 208 additions and 32 deletions
+3
View File
@@ -228,6 +228,9 @@ try { db.exec('ALTER TABLE users ADD COLUMN bizgaze_user_id TEXT'); } catch (e)
// External (non-Connect) invitee emails on a scheduled meeting (#4) — JSON array. They get an emailed
// guest join link instead of an in-app invite.
try { db.exec('ALTER TABLE scheduled_meetings ADD COLUMN guest_emails TEXT'); } catch (e) { /* exists */ }
// Lobby (#4): 1 = guests joining by link must be admitted by the host; 0 = they join directly. NULL is
// treated as "require approval" (safe default) by the signaling layer.
try { db.exec('ALTER TABLE scheduled_meetings ADD COLUMN lobby INTEGER'); } catch (e) { /* exists */ }
try { db.exec('CREATE INDEX IF NOT EXISTS idx_users_bizgaze_user_id ON users(bizgaze_user_id)'); } catch (e) { /* exists */ }
// When two accounts merge (#2), the merged-away row is deleted. This records old_id -> survivor so
// any lingering reference to the old id (a cached contact, an in-flight DM) resolves to the survivor