feat: WebSocket real-time + rAF render gate + guest board + screen picker

Classroom performance:
- WebSocket server (ws-server.js) for low-latency cursor & stroke preview
  Replaces HTTP POST per event → eliminates per-message auth overhead
  Session member cache (30s TTL) avoids SQLite query per WS message
  Fallback to HTTP POST when WS not connected
- Cursor throttle reduced 100ms → 33ms (~30fps)
- Stroke preview throttle reduced 50ms → 20ms
- whiteboard.js: render() is now rAF-gated (_doRender/_rafPending)
  Multiple render() calls within one frame collapse into one repaint
  document.hidden check — zero CPU when tab is in background
  visibilitychange listener restores canvas on tab focus

Guest board:
- guestClassroom.js route: public token-based read-only access
- guest-board.html: name entry + read-only whiteboard + SSE
- SSE: addGuestClient/removeGuestClient/emitToGuests

Screen share picker:
- Discord-style modal with tab switching (screen/window/tab)
- Live video preview before confirming share
- useExistingScreenStream() in ClassroomRTC

Fullscreen exit overlay:
- #cr-fs-exit-overlay button inside cr-board-wrap
- Visible only via CSS :fullscreen selector (touchpad users)

File sharing from library:
- Teacher picks file from library, sends as styled card in chat
- crDownloadLibraryFile() fetches with Bearer auth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-04-13 18:04:59 +03:00
parent 074ee5687b
commit fd29acbbdd
70 changed files with 12231 additions and 498 deletions
+7 -2
View File
@@ -862,7 +862,8 @@ db.exec(`
('feature_knowledge_map_enabled', '1'),
('feature_board_enabled', '1'),
('feature_biochem_enabled', '1'),
('feature_live_quiz_enabled', '1');
('feature_live_quiz_enabled', '1'),
('feature_classroom_enabled', '1');
`);
/* ── Performance indexes ───────────────────────────────────────────────── */
@@ -2773,8 +2774,9 @@ db.exec(`
UNIQUE(session_id, page_num)
)
`);
// Add template column to existing classroom_pages tables (idempotent)
// Add template + name columns to existing classroom_pages tables (idempotent)
try { db.exec(`ALTER TABLE classroom_pages ADD COLUMN template TEXT NOT NULL DEFAULT 'blank'`); } catch (_) { /* already exists */ }
try { db.exec(`ALTER TABLE classroom_pages ADD COLUMN name TEXT`); } catch (_) { /* already exists */ }
db.exec(`
CREATE TABLE IF NOT EXISTS classroom_strokes (
@@ -2854,6 +2856,9 @@ db.exec(`
)
`);
// Guest token for classroom sessions (public read-only whiteboard access)
try { db.exec("ALTER TABLE classroom_sessions ADD COLUMN guest_token TEXT UNIQUE"); } catch {}
// Persistent draw permissions (survives server restart)
db.exec(`
CREATE TABLE IF NOT EXISTS classroom_draw_permissions (