Skip presence ping for hidden tabs and refresh on visibility#20
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the Presence API Heartbeat inline script to better reflect user activity by factoring in Page Visibility, so background tabs do not keep a user marked as actively present and visibility restoration refreshes presence immediately.
Changes:
- Skip sending the
presence-pingpayload onheartbeat-sendwhendocument.visibilityState === 'hidden'. - On
visibilitychangeback to'visible', trigger an immediate Heartbeat tick viawp.heartbeat.connectNow()to re-establish presence without waiting for the next interval.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
westonruter
reviewed
May 13, 2026
josephfusco
added a commit
that referenced
this pull request
May 14, 2026
Page Visibility is universally supported across all browsers WP targets; the typeof string-check was belt-and-braces. Per Weston's review on PR #20.
josephfusco
added a commit
that referenced
this pull request
May 14, 2026
The previous commit's visibility guard only skipped the main presence-ping. The editor heartbeat handler — registered separately by wp_presence_enqueue_editor_ping() — kept refreshing presence-editor-ping in the per-post room, so a backgrounded post-edit tab still appeared as actively editing in the Active Posts widget. Apply the same hidden-state early-return inside the editor handler. Also softens the comment wording on the main guard to refer to the 'default TTL' rather than '60s TTL', since the TTL is filterable via wp_presence_default_ttl. Per Copilot's review on PR #20.
josephfusco
added a commit
that referenced
this pull request
May 14, 2026
Three e2e tests, all in tests/e2e/presence-visibility.test.js: - heartbeat-send omits presence-ping while document is hidden; on visibility restore, the ping reappears with a screen field. - heartbeat-send omits presence-editor-ping while the post-edit tab is hidden; on visibility restore, it returns with the correct post id. - A visibilitychange to 'visible' triggers an immediate wp.heartbeat.connectNow() call (counted via a method wrapper). Playwright headless doesn't fire real visibilitychange when a tab is backgrounded, so the tests stub document.visibilityState via Object.defineProperty and dispatch the event manually. The plugin's heartbeat-send handler runs before the test's one-shot listener, so the data object reflects whatever the guard did (or didn't do). Per Copilot's review on PR #20.
josephfusco
added a commit
that referenced
this pull request
May 14, 2026
- includes/heartbeat.php: use optional chaining on the connectNow guard inside the visibilitychange handler. Broaden the comment so it acknowledges that 'hidden' covers more than tab backgrounding (minimized windows, app switching). - tests/e2e/presence-visibility.test.js: replace the magic waitForTimeout(1000) warm-up with an explicit waitForFunction on wp.heartbeat. Clean up created posts via afterEach. Tighten the visibility-restore test by snapshotting the connectNow call counter before flipping visible and asserting the delta is at least one, rather than checking the absolute counter.
westonruter
approved these changes
May 14, 2026
Fixes #16. A user with a background tab on a presence-enabled page was still reported as actively present. Two changes: 1. In the heartbeat-send handler, return early when document.visibilityState === 'hidden' so the presence-ping is omitted. The existing 60s TTL cleans up the stale entry naturally. 2. On visibilitychange to 'visible', call wp.heartbeat.connectNow() so the user is re-established in the room within milliseconds rather than waiting for the next heartbeat interval. This is intentionally a soft fix (no explicit DELETE) so it can land independently of any pagehide-based cleanup.
Page Visibility is universally supported across all browsers WP targets; the typeof string-check was belt-and-braces. Per Weston's review on PR #20.
The previous commit's visibility guard only skipped the main presence-ping. The editor heartbeat handler — registered separately by wp_presence_enqueue_editor_ping() — kept refreshing presence-editor-ping in the per-post room, so a backgrounded post-edit tab still appeared as actively editing in the Active Posts widget. Apply the same hidden-state early-return inside the editor handler. Also softens the comment wording on the main guard to refer to the 'default TTL' rather than '60s TTL', since the TTL is filterable via wp_presence_default_ttl. Per Copilot's review on PR #20.
Three e2e tests, all in tests/e2e/presence-visibility.test.js: - heartbeat-send omits presence-ping while document is hidden; on visibility restore, the ping reappears with a screen field. - heartbeat-send omits presence-editor-ping while the post-edit tab is hidden; on visibility restore, it returns with the correct post id. - A visibilitychange to 'visible' triggers an immediate wp.heartbeat.connectNow() call (counted via a method wrapper). Playwright headless doesn't fire real visibilitychange when a tab is backgrounded, so the tests stub document.visibilityState via Object.defineProperty and dispatch the event manually. The plugin's heartbeat-send handler runs before the test's one-shot listener, so the data object reflects whatever the guard did (or didn't do). Per Copilot's review on PR #20.
- includes/heartbeat.php: use optional chaining on the connectNow guard inside the visibilitychange handler. Broaden the comment so it acknowledges that 'hidden' covers more than tab backgrounding (minimized windows, app switching). - tests/e2e/presence-visibility.test.js: replace the magic waitForTimeout(1000) warm-up with an explicit waitForFunction on wp.heartbeat. Clean up created posts via afterEach. Tighten the visibility-restore test by snapshotting the connectNow call counter before flipping visible and asserting the delta is at least one, rather than checking the absolute counter.
4e17888 to
3fbba1a
Compare
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.
Closes #16.
A user with a background tab on a presence-enabled page was still reported as actively present.
Change
Two small additions to the inline heartbeat script:
heartbeat-sendhandler, return early whendocument.visibilityState === 'hidden'. The presence-ping is omitted, and the existing 60 s TTL plus the cron sweep clean up the stale entry naturally.visibilitychangeto'visible', callwp.heartbeat.connectNow()so the user is re-established in the room within milliseconds rather than waiting for the next heartbeat interval.This is intentionally a soft fix (no explicit
DELETE) so it can land independently of any pagehide-based cleanup. If we later add pagehide DELETEs (see #15 / #19), a small follow-up could fire the sameleave()on hidden as well; out of scope here to keep this diff minimal.Files
includes/heartbeat.phpVerification
With wp-env running, log in as editor in window A and as admin in window B. From the admin window, watch the Who's Online widget. In window A, switch to a different tab — within roughly a heartbeat interval the editor's screen should stop updating. Switch back to the admin tab and the editor's presence should refresh on the next tick.