-
Notifications
You must be signed in to change notification settings - Fork 61
fix: flickering of active collaborator icons between states #3951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: flickering of active collaborator icons between states #3951
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3951 +/- ##
==========================================
- Coverage 88.82% 88.79% -0.04%
==========================================
Files 422 422
Lines 19069 19069
==========================================
- Hits 16938 16932 -6
- Misses 2131 2137 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
suggestion from @stuartc, "use phoenix channel callback instead of timeouts to keep awareness active". |
561dffc to
43b4845
Compare
|
update
|
…rator-avatar-dissapears-on-the-editor-instead-of-greying-out
elias-ba
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @doc-han! The store implementation and caching mechanism look great. However, I noticed the ActiveCollaborators.tsx component wasn't updated to use the new 12-second threshold. The component still uses lessthanmin(user.lastSeen, 2) on line 47, which is probably causing the JavaScript tests to fail.
stuartc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have concerns about the core approach here. This implementation
conflates the Yjs awareness heartbeat (keeping the connection alive)
with user presence (activity/engagement). The result is that user
presence state is now driven by each browser's internal throttling
policies rather than actual user behavior. What's happening:
- The frozen timestamp technique keeps awareness alive during throttling
- But we're then using that same lastSeen timestamp to determine if
users are "active" or "inactive" - This means presence state is an indirect side-effect of browser
throttling, not a direct measurement of user engagement
The constraints this creates:
- Browser throttles → lastSeen updates slow → appears inactive
- Browser active → lastSeen updates normally → appears active
We're treating the browser's power-saving policies as a signal for user
presence. This varies by: Browser, device & OS, battery state, power
profile...
Extensibility problem:
Where would we add actual presence detection?
If we wanted to:
- Check document.visibilityState directly
- Track user interactions (mouse, keyboard)
- Distinguish "tab switched for a few seconds" from "user left computer"
Would we keep extending lastSeen? That field is already serving two
purposes:
- Keep Yjs awareness alive/changing (its original job)
- Indicate user activity (what we're overloading it with)
We discussed the other day: Heartbeat and presence are separate
concerns. The heartbeat exists to prevent connection timeouts - it's
plumbing. User presence is a product feature about engagement/activity.
Mixing them means we can't adjust one without affecting the other.
Example: If we want to show users as inactive faster (say 5 seconds), we
can't - we're locked to the 10-30s window by Yjs's 30-second timeout. If
we want to keep users visible longer after they close a tab, we add
caching layers that delay all removals by 60+ seconds.
The 60-second removal time: Users disappear 60-70 seconds after closing
a tab because we need the cache to smooth over throttling-induced
flickering. But that's a workaround for using throttling as a presence
signal in the first place.
Request: What would it take to separate these? Could we: Use lastSeen
only for connection health (keep current 10s updates) Add a separate
lastActivity and/or lastState field to awareness (active, away, idle) +
timestamp
Update that field based on visibility API, not throttling side-effects
Let the UI read lastActivity and/or lastState instead of calculating
from lastSeen This would give us direct control over presence without
fighting browser throttling behavior. The frozen timestamp technique
might not be needed since it's value only needs to actually change in
order to keep the awareness around. But wouldn't drive the user-facing
presence indicator. I want to understand: what blocked this approach? Is
there a technical constraint I'm missing, or was the lastSeen
overloading the simpler path?
Description
Implements active, inactive and unavailable states for active collaborators on a workflow.
active - a user is active when a workflow is open and the user is actively on the tab.
inactive - a user is inactive after 12s if they leave the tab where the workflow is open.
unavailable - a user is removed from active collaborators after approx. 1min of closing the tab where the workflow is open.
Main issue
[a] Throttling of browser timers is our biggest villain in this PR.
What problems does it cause?
Hence, when a user leaves a tab, they will be marked as offline when throttling kicks in, and when after being throttled, their event finally goes through, they then get marked back as online. making them flicker
[b] Another thing to be aware of 10secs interval
Every 10secs we actually update the user's
lastSeentoDate.now()but then when the user leaves the tab, we don't update thislastSeenvalue. And because there's no state change/diff happening when the user leaves the tab. No event is actually triggered to keep ydoc live.Approach in this PR
When user leaves the tab. for every 10secs, we increment lastSeen by 1ms which will trigger an event to keep ydoc live. This is to battle [b]
We have a small cache for activeCollaborators that lives for 60secs. when a user shows up as an active-collaborator we keep him for 60secs max. this 60secs is renewed when they show up again in the new set of active collaborators we receive. This resolves [a]
and 2. work hand in hand to smoothen the active collaborators regardless of the ydoc and browser throttling limits.
Side Effect
When a user closes a tab or a workflow. They disappear from other users active-collaborators after 60secs. but users would know inactive collaborators real quick(12secs)
Closes #3931
Validation steps
Note: Some values got changed.
eg.
Additional notes for the reviewer
AI Usage
Please disclose how you've used AI in this work (it's cool, we just want to know!):
You can read more details in our Responsible AI Policy
Pre-submission checklist
:owner,:admin,:editor,:viewer)