Repository navigation
Apply the owner's rulings on the Internal Comments port - #23
Merged
Merged
Conversation
A site that goes back to 1.2.4 after updating stores its new internal comments approved, and the migration that moves v1's comments does not run again on the next update, so those comments stay public. The owner accepts the gap, given the plugin's installation count, and has the Upgrade Notice say so before anyone updates. The notice keeps under wordpress.org's 300 characters by trimming its other sentences. Assisted-by: Claude Code:claude-opus-5-5
The refusal of a private comment the site disallows said only that the user could not post one here, which reads as a rule rather than as the fate of the text just sent. The owner wants it to say the comment was not posted and why. The row expects the refusal's code and a message saying the comment was not posted. Assisted-by: Claude Code:claude-opus-5-5
The REST route refuses a private comment where the site's setting or the user's capabilities no longer allow one, and its message said only that the user could not post one here, leaving open whether the text just sent was stored. The message now says the comment was not posted because the site's settings no longer let the user post private internal comments, and how to post it for everyone instead. Assisted-by: Claude Code:claude-opus-5-5
Core trashes, deletes, restores and moderates a comment on edit_comment alone: its Comments screen, its AJAX and bulk actions, and XML-RPC's wp.deleteComment. With edit_comment mapped to the edit capability only, a role given edit rights without trash rights could trash or delete internal comments there. The owner ruled that edit_comment on an internal comment asks for both capabilities. The row gives editors edit_others without trash_others and expects the meta box's edit capability granted, its trash capability refused and edit_comment refused, then edit_comment granted once both are held. Assisted-by: Claude Code:claude-opus-5-5
Core trashes, deletes, restores and moderates a comment on edit_comment alone, through the Comments screen, its AJAX and bulk actions, and XML-RPC's wp.deleteComment, so a role given edit rights without trash rights could trash or delete internal comments there. Permissions maps edit_comment on an internal comment to the edit and the trash capability together. The plugin's own routes check each meta capability on its own, so a role that can only edit still edits in the meta box; core's comment edit screen opens only for users who hold both, as AGENTS.md's limitations now say. Assisted-by: Claude Code:claude-opus-5-5
WordPress's comment edit screen offers an internal comment's author, status and date, which a save keeps as stored, so a change there reads "Comment updated" and is dropped; its status line is blank and no status option is chosen. The owner ruled that the screen hides them. The row expects a style hiding the author box, the status section and the date section on the edit screen of an internal comment, and none for a public one. Assisted-by: Claude Code:claude-opus-5-5
WordPress's comment edit screen offered an internal comment's author, status and date, which a save keeps as stored, so a change there read "Comment updated" and was dropped; the status line was blank, with no status option chosen. On that screen, AdminScreens hides the author box, the status section and the date section of an internal comment through an inline style, so the screen shows only what saving changes: the text, the author's privacy choice and the revisions. The update guard still keeps the hidden fields, which the form posts, as stored. A browser row, written after the style and checked failing with it switched off, opens the screen from the Comments screen's Edit link. Assisted-by: Claude Code:claude-opus-5-5
The Comments screen leaves internal comments out of its list for users without the table capability, but WordPress counts its views with queries of their own, so an editor saw a Trash count that included the trashed internal comments the list hid. The owner ruled that the count match the list. The row trashes an internal and a public comment and expects an editor's Trash count one lower than an administrator's on that screen, with the cached counts still WordPress's own; it sets the screen without firing current_screen, which another plugin in the shared environment hooks with admin-only code. Assisted-by: Claude Code:claude-opus-5-5
AGENTS.md still said core's comment edit screen shows an internal comment's author, date and status, which e5cf5e1 hid. The limitation now names what holds: the style relies on core's element ids, and saving keeps those fields as stored either way. Assisted-by: Claude Code:claude-opus-5-5
For users without the table capability, the Comments screen leaves internal comments out of its list, but WordPress counts its views with queries of its own, so an editor saw a Trash count that included the trashed internal comments the list hid. The counts are cached for every user, by WordPress and by WooCommerce, which answers through the same filter, and the admin menu reads them before the screen exists, so the exclusion cannot sit in the count queries. AdminScreens filters wp_count_comments at priority 20, after WooCommerce: on the Comments screen, for a user without the table capability, it takes the counts answered so far, or WordPress's own, and returns a copy whose Trash count leaves out the trashed internal comments, without writing to either cache. The row now relies on the plugin's own registration, which runs in every request, clears WooCommerce's incremental counts that earlier tests leave behind, and covers both paths; it fails with the registration switched off. AGENTS.md's limitations add that a role holding the list capability without see_private can see a page run short. Assisted-by: Claude Code:claude-opus-5-5
The Comments screen row that grants editors the table capability leaves the editor as the current user after taking the capability back from the role, and wp_set_current_user() keeps that user object for the same id, so in some random orders (seed 1793450840) the count row ran as an editor who could still list internal comments and saw no adjustment. The row reads the editor's capabilities again after switching to them. Assisted-by: Claude Code:claude-opus-5-5
WooCommerce answers the comment counts from a cache it adjusts on each status change, while the plugin moves comments back under the internal status, and moves v1's in bulk, with direct writes it does not announce, so the counts kept the statuses those comments passed through: a restored comment stayed in Pending, and v1's moved comments stayed in Approved, until WooCommerce's cache expired. The owner ruled that the move back be announced as a status change, and that the migration reset WooCommerce's counts after each batch. One row restores an internal comment and tries to approve it, then expects WooCommerce's cached counts to equal a fresh count; the batched-move row caches the counts before the move and expects the same after it. Assisted-by: Claude Code:claude-opus-5-5
WooCommerce answers the comment counts from a cache it adjusts on each announced status change, and the plugin moved comments back under the internal status, and v1's in bulk, without announcing it, so a restored comment stayed in Pending and v1's moved comments in Approved until WooCommerce's cache expired. Comments writes the status back through put_back(), which reports the status it replaced. After a status change, announce_kept_internal() announces the move back through wp_transition_comment_status() at the last priority of transition_comment_status, since wp_set_comment_status() announces its own change only after the action where the write-back runs; after an insert, which WordPress announces no status for, keep_inserted_internal() announces it at once. The migration resets WooCommerce's counts after each batch, as announcing up to 10,000 moves per batch would run every listener that many times. The insert path's row, added with the change, fails with its announcement off. Assisted-by: Claude Code:claude-opus-5-5
A reply written while its thread was public makes its author an owner of that reply's thread once the comment above it, made private later, is deleted permanently, so the author can read replies its privacy had hidden from them. The owner ruled that a comment can turn private only before anyone replies to it. The guard row expects the author and code running as no user refused when turning a replied comment private, while a private one with replies still turns public and a comment without replies turns private; the edit screen row expects the privacy choice withheld from a replied public comment and offered on a replied private one. Assisted-by: Claude Code:claude-opus-5-5
A reply written while its thread was public made its author an owner of that reply's thread once the comment above it, made private later, was deleted permanently, so the author could read replies the thread's privacy had hidden from them. The privacy guard refuses turning a comment private once any internal comment replies to it, whoever asks, while a private comment with replies can still turn public; core's comment edit screen offers the choice on such a comment only while it is private. Every reply under a private comment then comes from a user who could see it, so the replies made private on a deletion give ownership only to users already in the thread. dws_ic_has_replies() counts replies of any status for both. AGENTS.md's limitation narrows to threads 1.2.4 made private after others replied. The guard row's check of a comment turning private now reads the outcome, since update_comment_meta() returns the new row's id when the key was absent. Assisted-by: Claude Code:claude-opus-5-5
Claude's review of the rulings branch found that while v1's comments are being moved, the type exclusion already leaves internal comments out of the counts core and WooCommerce compute, so the Comments screen's adjustment took the trashed ones out a second time; and that the counts cached during that window outlived the move, as only a batch that moved comments reset WooCommerce's, never the walk's end. One row expects an editor's Trash count equal to an administrator's while the move is pending; the other caches the counts while it is pending, walks to the end, and expects them equal to a fresh count. Assisted-by: Claude Code:claude-opus-5-5
Claude's review of the rulings branch found that while v1's comments are being moved, the type exclusion already leaves internal comments out of every count, so the Comments screen's adjustment took the trashed ones out a second time; and that counts cached in that window outlived the move, as only a batch that moved comments reset WooCommerce's. dws_ic_is_v1_move_pending() is the one home of the pending check, which Comments' exclusion and the count adjustment share; the adjustment steps aside while the move is pending. The migration clears core's global counts and WooCommerce's after a batch that moved comments and at the walk's end. The review's cleanups go with it: keep_inserted_internal() fetches the comment only when it moved one, and keep_post_comments_internal() calls put_back() directly. AGENTS.md's limitations name the posts a listing role cannot edit beside private comments, with view counts, and add_comment_meta() passing past both privacy rules. Assisted-by: Claude Code:claude-opus-5-5
Codex's first round on the rulings branch found that WooCommerce leaves comments on products out of every comment query that names no type, the counts included, but skips that exclusion for a query that names one, so the Trash adjustment, which counted the trashed internal comments by naming their type, also took out the ones on products the count never held. The row trashes an internal comment on a product and a public one on a post, and expects an editor's Trash count equal to an administrator's. Assisted-by: Claude Code:claude-opus-5-5
WooCommerce leaves comments on products out of every comment query that names no type, and skips that exclusion for one that names a type, so subtracting the trashed internal comments, counted by naming their type, also took out the ones on products the count never held. The adjustment counts the Trash again with no type named and the internal type left out through a comments_clauses filter it adds for that query alone, so WooCommerce's exclusion, and any other plugin's on such queries, applies to it as it did to the count it replaces. Assisted-by: Claude Code:claude-opus-5-5
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
Applies the rulings on the Internal Comments port's open decisions: core's own comment paths are held to the trash capability, core's comment edit screen shows only what saving changes, the Comments screen's Trash count matches its list, WooCommerce's comment counts follow internal comments moved back under their status, and a comment can turn private only before anyone replies.
Type of change
Checklist
composer changelog:addin the changed plugin's directory, or at the root for the monorepo itself)composer quality-checkpasses locally in every changed pluginreadme.txtupdated if runtime requirements changedWhat changes
edit_commenton an internal comment needs both the edit and the trash capability, so core's Comments screen actions and XML-RPC cannot trash or delete without trash rights; core's comment edit screen hides an internal comment's author, status and date, which saving keeps as stored.Breaking changes
Not verified locally