Skip to content

fix: merge the post lock entry into the editor's presence entry - #284

Merged
josephfusco merged 2 commits into
WordPress:mainfrom
jigneshbhavani:fix/134-merge-post-lock-into-editor-entry
Aug 14, 2026
Merged

fix: merge the post lock entry into the editor's presence entry#284
josephfusco merged 2 commits into
WordPress:mainfrom
jigneshbhavani:fix/134-merge-post-lock-into-editor-entry

Conversation

@jigneshbhavani

@jigneshbhavani jigneshbhavani commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

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 in the plugin parses.

The lock is now a locked flag on the editor's own entry, read from the wp-refresh-post-lock payload 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. locked means this user's editor is sending lock refreshes, not that they hold the lock. Core sends wp-refresh-post-lock from any open editor, including one showing the takeover dialog. That is what the lock- 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 when presence-editor-ping covers 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.php also drops lock-{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:90 and admin-bar.php:109 all 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 old lock- write, which fails that guard.

Verified in a browser on wp-env against both branches: on main the room holds editor-1 and lock-1 and the dashboard reads "2 editors" for one person; on this branch the same steps give one row with "locked":true and the widget agrees with GET /presence/rooms reporting user_count: 1. Note the post lock heartbeat only runs when a site has more than one user, because core hooks the lock dialog under get_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.

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
@github-actions

Copy link
Copy Markdown
Contributor

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props bejignesh, joefusco.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown
Contributor

▶ Preview in WordPress Playground

Open in WordPress Playground

Boots 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 da77dfb262e1b74d23defea4cb07905387abb5cf. Auto-updates when you push.

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.14%. Comparing base (41fb068) to head (da77dfb).

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     
Flag Coverage Δ
multisite 78.14% <80.00%> (+1.94%) ⬆️
phpunit 76.85% <80.00%> (+1.95%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@josephfusco josephfusco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

@josephfusco
josephfusco merged commit ea060d6 into WordPress:main Aug 14, 2026
20 checks passed
@jigneshbhavani
jigneshbhavani deleted the fix/134-merge-post-lock-into-editor-entry branch August 14, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resolve double-entry behavior in post editor rooms

2 participants