fix(orbital): Fix event sampling and Safari SSE connection dropping#14
Merged
rahulchhabria merged 6 commits intomasterfrom Mar 5, 2026
Merged
fix(orbital): Fix event sampling and Safari SSE connection dropping#14rahulchhabria merged 6 commits intomasterfrom
rahulchhabria merged 6 commits intomasterfrom
Conversation
…source servers The stale-burst guard was using Math.abs() which dropped events in both directions. Source servers with clocks ~5s ahead of the client were causing live events to be incorrectly discarded. Only drop events that are genuinely old (>10s in the past) — stale SSE bursts always arrive with past timestamps. Events with future timestamps are fine; they just reflect server/client clock drift. Also loosens the threshold from 5s to 10s to give more margin for slow SSE queue drains before a tab comes back into focus. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three issues caused Safari to stop rendering events: 1. visibilitychange unreliable on Safari iOS — added pageshow/pagehide listeners as fallbacks since Safari sometimes fires pagehide when switching apps but skips the matching visibilitychange on return, leaving windowInFocus stuck at false and silently dropping all events. 2. SSE reconnect silently fails on Safari — onerror now force-reconnects on both CLOSED and CONNECTING states. Safari fires onerror with readyState=CONNECTING but never actually reconnects, leaving the connection dead indefinitely. 3. No last-resort recovery — added a 30s watchdog timer that resets on every received SSE message and force-closes/reconnects if silent, catching any edge case where the connection dies without an error event. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ad lastMessageAt variable On non-Safari browsers, readyState === CONNECTING during onerror means the browser is already handling reconnection with native exponential backoff. Forcing a manual reconnect in that case bypasses backoff and increases server load unnecessarily. Also remove the lastMessageAt variable which was assigned but never read — the watchdog relies entirely on setTimeout/clearTimeout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
…n background tabs
Two fixes:
1. Watchdog/heartbeat: During legitimate low-traffic periods the 30s watchdog
would fire and force-reconnect in a loop. Add a server-side goroutine that
sends a JSON ping ({}) every 20s; the client onStreamMessage ignores non-array
messages so real event processing is unaffected.
2. pageshow background tab bug: pageshow fires for all page loads (not just
bfcache restorations), so opening a tab in the background would let pageshow
override the document.hidden initialisation and mark windowInFocus=true. Guard
the pageshow handler with a document.hidden check to match the Safari bfcache
fallback intent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BYK
approved these changes
Mar 5, 2026
static/orbital.js
Outdated
| source = null; | ||
| } | ||
| connectStream(); | ||
| }, 30000); |
Member
There was a problem hiding this comment.
30 seconds is a very long time for Sentry events. I'd make it 5 to keep responsiveness.
main.go
Outdated
| // Heartbeat: send a ping every 20s so the client watchdog doesn't | ||
| // fire during legitimate low-traffic periods. | ||
| go func() { | ||
| ticker := time.NewTicker(20 * time.Second) |
Member
There was a problem hiding this comment.
Accordingly, this should be around 5 seconds or less
Per BYK's feedback: 30s is too long for a Sentry events stream. Watchdog now fires after 5s of silence; server heartbeat ticks every 3s to keep connections alive without triggering the watchdog. Co-Authored-By: Claude <noreply@anthropic.com>
camera.position.set(0, y, z) was resetting the x-component to 0 on breakpoint transitions, snapping the globe back to its start angle when autoRotate had moved the camera away from the Z axis. Decompose the current camera position into its azimuthal angle before applying the new distance/y-offset, then recompose at that same angle so only zoom and vertical offset change. Co-Authored-By: Claude <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two bugs were causing events to be dropped or stop rendering entirely.
Clock skew drops live events
The stale-burst guard used
Math.abs()which dropped events in both directions. Source servers with clocks ~5s ahead of the client were causing live events to be incorrectly discarded. Fixed to only drop events that are genuinely old (>10s in the past) — stale SSE bursts always arrive with past timestamps, not future ones.Safari stops rendering after ~30s
Three compounding issues caused Safari to silently stop receiving events:
visibilitychangestuck — Safari iOS sometimes firespagehidewhen switching apps but skips the matchingvisibilitychangeon return, leavingwindowInFocus = falsepermanently. Addedpageshow/pagehidelisteners as reliable Safari fallbacks.onerrorwithreadyState=CONNECTINGbut never actually reconnects. The old handler only retried onCLOSED. Now force-reconnects on both states with a clean teardown.Test plan
🤖 Generated with Claude Code