2 Commits
Author SHA1 Message Date
GHMiranda 75750cf226 Merge pull request 'ralph/collaboration-and-character-variables' (#9) from ralph/collaboration-and-character-variables into developing
Reviewed-on: #9
2026-01-25 00:11:20 +00:00
Gustavo Henrique Santos Souza de MirandaandClaude Opus 4.5 cd3ecc4507 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>
2026-01-24 19:35:00 -03:00
2 changed files with 18 additions and 8 deletions
@@ -691,7 +691,7 @@ function FlowchartEditorInner({ projectId, projectName, userId, userDisplayName,
useEffect(() => {
const handleVisibilityChange = () => {
if (!document.hidden) {
realtimeRef.current?.notifyActivity()
realtimeRef.current?.notifyVisibilityResumed()
}
}
+17 -7
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()
}
}