fix: only force-reconnect on tab visibility change, not on activity gaps

The 60s stale threshold was triggering during normal use (reading, thinking)
causing constant reconnects and presence join/leave toast spam. Now force-
reconnect only happens when the tab was hidden for 3+ minutes and becomes
visible again. Regular activity (mouse/keyboard) only resets the inactivity
timer without ever forcing a reconnect.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gustavo Henrique Santos Souza de Miranda 2026-01-24 19:35:00 -03:00
parent 3b19f58e26
commit cd3ecc4507
2 changed files with 18 additions and 8 deletions

View File

@ -691,7 +691,7 @@ function FlowchartEditorInner({ projectId, projectName, userId, userDisplayName,
useEffect(() => {
const handleVisibilityChange = () => {
if (!document.hidden) {
realtimeRef.current?.notifyActivity()
realtimeRef.current?.notifyVisibilityResumed()
}
}

View File

@ -44,7 +44,7 @@ const RECONNECT_BASE_DELAY_MS = 1000
const RECONNECT_MAX_DELAY_MS = 30_000
const INACTIVITY_TIMEOUT_MS = 5 * 60_000 // 5 minutes of inactivity before pausing
const CONNECTION_TIMEOUT_MS = 15_000 // 15s timeout for initial connection
const STALE_THRESHOLD_MS = 60_000 // 60s of inactivity = force fresh reconnect on return
const STALE_THRESHOLD_MS = 3 * 60_000 // 3 minutes hidden before forcing fresh reconnect
export class RealtimeConnection {
private channel: RealtimeChannel | null = null
@ -262,21 +262,31 @@ export class RealtimeConnection {
/**
* Notify that user activity has occurred, resetting the inactivity timer.
* If the connection was paused due to inactivity, it will resume.
* If the connection was idle for a significant period, force a fresh reconnect
* to ensure the WebSocket isn't stale.
*/
notifyActivity(): void {
if (this.isDestroyed) return
this.lastActivityTime = Date.now()
this.resetInactivityTimer()
if (this.isPaused) {
this.resume()
}
}
/**
* Called when the tab becomes visible again. If the tab was hidden for a
* significant period, force a fresh reconnect to handle stale WebSockets.
*/
notifyVisibilityResumed(): void {
if (this.isDestroyed) return
const now = Date.now()
const idleDuration = now - this.lastActivityTime
const hiddenDuration = now - this.lastActivityTime
this.lastActivityTime = now
this.resetInactivityTimer()
if (this.isPaused) {
this.resume()
} else if (idleDuration > STALE_THRESHOLD_MS && this.channel) {
// Connection may appear alive but WebSocket could be stale after idle.
// Force a fresh reconnect to ensure broadcasts actually work.
} else if (hiddenDuration > STALE_THRESHOLD_MS && this.channel) {
this.forceReconnect()
}
}