fix: chat-open scroll regression, SFU screen reliability, unanswered calls, selected-chat border
- REGRESSION (More#1): removed overflow-anchor:none (it stopped the view from staying at the bottom as images settle) + renderThread re-asserts scroll-to-bottom after late content, unless a 'New messages' divider is shown → chats open at the latest message. - #9: dynacast off — a screen-share layer was being paused unless a camera track was also flowing (blank / only-with-camera / slow). Now every published track flows. - More#7/#9: 1:1 calls track 'answered'; unanswered calls auto-end after ~40s (caller no longer stuck ringing) and post 'Missed call' instead of a duration; answered calls show duration from the answer time. meeting-ended reason 'unanswered' → 'No answer'. - More#6: selected chat now has a thin yellow (brand) boundary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-4
@@ -95,10 +95,18 @@ function startDmCall(me, otherId, teamId) {
|
||||
let room; do { room = A.numericCode(6); } while (meetingRooms.has(room));
|
||||
meetingRooms.set(room, new Map());
|
||||
const byName = me.name || me.email;
|
||||
const call = { room, startedAt: now(), startedBy: me.id, startedByName: byName, users: [me.id, otherId], teamId };
|
||||
const call = { room, startedAt: now(), startedBy: me.id, startedByName: byName, users: [me.id, otherId], teamId, answered: false };
|
||||
// Log to history (both participants) so the call shows under Past meetings with its transcript.
|
||||
try { const hid = A.id(); R.scheduledMeetings.create({ id: hid, teamId, groupId: null, roomCode: room, title: 'Direct Call', description: null, scheduledAt: now(), createdBy: me.id, participants: [me.id, otherId] }); call.historyId = hid; } catch (_) {}
|
||||
dmCalls.set(key, call); roomToDmCall.set(room, key); roomHost.set(room, me.id); // caller = host
|
||||
// #9 (unanswered): if the callee never joins within the ring window, auto-end and mark it missed —
|
||||
// so the caller isn't stuck "ringing" forever.
|
||||
call.ringTimer = setTimeout(() => {
|
||||
if (call.answered) return;
|
||||
const peers = meetingRooms.get(room);
|
||||
if (peers) { for (const [, p] of peers) { if (p.ws && p.ws.readyState === 1) { try { p.ws.send(JSON.stringify({ type: 'meeting-ended', reason: 'unanswered' })); } catch (_) {} p.ws._meetingRoom = null; } } meetingRooms.delete(room); }
|
||||
endDmCallByRoom(room);
|
||||
}, 40000);
|
||||
// A viewer-relative activity line: the caller sees "You started a call", the callee sees the name.
|
||||
const mid = A.id();
|
||||
R.messages.send({ id: mid, teamId, senderId: me.id, recipientId: otherId, body: '📞 Started a call', msgType: 'call-start' });
|
||||
@@ -115,10 +123,11 @@ function endDmCallByRoom(room, silent) {
|
||||
const call = dmCalls.get(key);
|
||||
roomToDmCall.delete(room); dmCalls.delete(key); roomHost.delete(room);
|
||||
if (!call) return;
|
||||
if (call.ringTimer) { try { clearTimeout(call.ringTimer); } catch (_) {} }
|
||||
if (call.historyId && call.teamId) { try { R.scheduledMeetings.end(call.historyId, call.teamId); } catch (_) {} } // mark history past
|
||||
// "Call ended · duration" activity line in the DM (shown to both) — skipped on decline.
|
||||
// Activity line: a duration ONLY if the call was answered; otherwise "Missed call" (#7/#9).
|
||||
if (!silent) try {
|
||||
const mid = A.id(); const body = '📞 Call ended · ' + fmtDur(now() - call.startedAt);
|
||||
const mid = A.id(); const body = call.answered ? ('📞 Call ended · ' + fmtDur(now() - (call.answeredAt || call.startedAt))) : '📞 Missed call';
|
||||
R.messages.send({ id: mid, teamId: call.teamId, senderId: call.startedBy, recipientId: call.users.find((u) => u !== call.startedBy) || '', body, msgType: 'call-end' });
|
||||
const m = R.messages.byId(mid); const dto = { id: m.id, from: call.startedBy, to: m.recipient_id, conversation_id: null, body, created_at: m.created_at, system: true, evt: 'call-end' };
|
||||
call.users.forEach((uid) => { try { CHAT.pushToUser(uid, { type: 'chat-message', message: dto }); } catch (_) {} });
|
||||
@@ -126,6 +135,13 @@ function endDmCallByRoom(room, silent) {
|
||||
call.users.forEach((uid, i) => { try { CHAT.pushToUser(uid, { type: 'dm-call', active: false, with: call.users[1 - i], room }); } catch (_) {} });
|
||||
}
|
||||
|
||||
// Mark a 1:1 call answered when the callee (anyone other than the caller) joins its room, so the end
|
||||
// message shows a real duration (from answer) and the unanswered timeout stands down.
|
||||
function markDmAnswered(room, userId) {
|
||||
const key = roomToDmCall.get(room); if (!key) return;
|
||||
const call = dmCalls.get(key); if (!call) return;
|
||||
if (userId && userId !== call.startedBy && !call.answered) { call.answered = true; call.answeredAt = now(); if (call.ringTimer) { try { clearTimeout(call.ringTimer); } catch (_) {} call.ringTimer = null; } }
|
||||
}
|
||||
// Called from signaling when any mesh room empties.
|
||||
function endCallByRoom(room) { endGroupCallByRoom(room); endDmCallByRoom(room); }
|
||||
|
||||
@@ -152,4 +168,4 @@ function declineDmCall(room, byUser) {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
module.exports = { startGroupCall, startDmCall, endGroupCallByRoom, endDmCallByRoom, endCallByRoom, declineDmCall, finalizeTranscript, meetingContext, fmtDur, pairKey };
|
||||
module.exports = { startGroupCall, startDmCall, endGroupCallByRoom, endDmCallByRoom, endCallByRoom, declineDmCall, markDmAnswered, finalizeTranscript, meetingContext, fmtDur, pairKey };
|
||||
|
||||
Reference in New Issue
Block a user