fix: merge the post lock entry into the editor's presence entry - #284
Conversation
Opening a post editor wrote two rows to the same post room for the same
user, editor-{user_id} from the editor heartbeat and lock-{user_id} from
the post-lock bridge. Both stored identical state and differed only by a
client_id prefix that nothing parses.
The lock is now a `locked` flag on the editor's own entry, derived from
the wp-refresh-post-lock payload that arrives on the same tick. That
keeps the liveness signal the split encoded: a tick carrying no refresh
clears the flag, which is what the second row's expiry used to say.
The bridge stands down when the editor ping already covers the post, so
this removes a write per tick per editing user rather than relocating
one, and drops the now-empty lock- client from the pagehide cleanup.
Fixes WordPress#134
The merge above removes the duplicate row this widget was counting, but nothing guarantees one row per user in a room, and this is the only aggregation path that did not already dedupe. wp_get_active_rooms(), the post list column and the admin bar all key by user id. Rows arrive newest first, so the first entry seen for a user is the freshest and decides their status. Refs WordPress#134
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
▶ Preview in WordPress PlaygroundBoots a fresh WordPress with this PR's presence-api build, seeds 5 demo users, and drops you on the dashboard. Stress-test variant: 40 demo users · Built from |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #284 +/- ##
============================================
+ Coverage 76.19% 78.14% +1.94%
- Complexity 176 178 +2
============================================
Files 14 14
Lines 1869 1871 +2
============================================
+ Hits 1424 1462 +38
+ Misses 445 409 -36
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Ran this locally in a worktree against both branches — PHPCS/PHPStan clean, full suite passes (205/205, 5 skipped), and the one multisite failure I saw also shows up on unmodified main so it's not a regression; walked through the bridge's stand-down logic and the widget's dedupe by hand and both check out.
Thank you for working on this!
Opening a post editor wrote two rows to the same post room for the same user,
editor-{user_id}from the editor heartbeat andlock-{user_id}from the post-lock bridge. Both stored identical state and differed only by aclient_idprefix that nothing in the plugin parses.The lock is now a
lockedflag on the editor's own entry, read from thewp-refresh-post-lockpayload that arrives on the same tick. That keeps the one signal the split encoded: a tick carrying no refresh clears the flag, which is what the second row ageing out used to say.Worth being explicit, since the flag is easy to over-read.
lockedmeans this user's editor is sending lock refreshes, not that they hold the lock. Core sendswp-refresh-post-lockfrom any open editor, including one showing the takeover dialog. That is what thelock-row meant too, so this preserves the meaning rather than changing it.The bridge stands down instead of writing
wp_presence_bridge_post_lock()returns early whenpresence-editor-pingcovers the same post, since the editor handler has already written that row from the same payload. That removes a write per tick per editing user rather than relocating one, which is the baseline change #131 is waiting on. The bridge still writes when a client refreshes the core lock without sending the presence ping.heartbeat.phpalso dropslock-{user_id}from the pagehide cleanup, since that client no longer exists.Second commit: the widget counts people, not rows
Nothing guarantees one row per user in a room, and the Active Posts widget was the only aggregation path that did not already dedupe.
wp_get_active_rooms(),post-list.php:90andadmin-bar.php:109all key by user id. It also covers the upgrade window:lock-*rows written by the old code survive up to the TTL after deploy and the pagehide cleanup no longer targets them, so the dedupe is what keeps the dashboard honest until they expire.Testing
wp_presence_editor_heartbeat_received()had no coverage, so this adds it alongside the new behaviour, plus the regression guard that one editing user occupies one row however many handlers see the tick. Every guard was mutation checked, including restoring the oldlock-write, which fails that guard.Verified in a browser on wp-env against both branches: on
mainthe room holdseditor-1andlock-1and the dashboard reads "2 editors" for one person; on this branch the same steps give one row with"locked":trueand the widget agrees withGET /presence/roomsreportinguser_count: 1. Note the post lock heartbeat only runs when a site has more than one user, because core hooks the lock dialog underget_user_count() > 1(edit-form-advanced.php:28), so on a single user site neither row is written and the bug is invisible.205 tests, 5 skipped (multisite only). PHPCS, PHPStan and Playwright clean.
Fixes #134
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Tracing the consumers of presence rows, implementing the merge, and writing the tests. I confirmed each test fails before the change and passes after, mutation checked every regression guard, verified the behaviour in a browser against both branches, and ran the full PHPUnit and Playwright suites plus PHPCS and PHPStan.