fix: Delete terms by their remote id and surface deletion failures - #23294
Open
dcalhoun wants to merge 8 commits into
Open
fix: Delete terms by their remote id and surface deletion failures#23294dcalhoun wants to merge 8 commits into
dcalhoun wants to merge 8 commits into
Conversation
deleteTerm() identified the term with TermModel.id, the local database row id, so the API was asked to delete an id belonging to another term or to nothing at all and answered 404 rest_term_invalid. Creating works because it needs no id; deleting a category the app had just created failed every time. Every sibling uses the remote identity — TaxonomyXMLRPCClient sends getRemoteTermId(), TaxonomyRestClient uses the slug, and updateTerm() in this same class already sends term.remoteTermId. The success path had the mirror image of the same slip, passing the local id as the confirmation model's remoteTermId. The existing delete tests stub WpApiClient.request wholesale, so the id sent was never observed and two of them asserted the wrong value. Add a test that runs the request against a stubbed executor and fails on the id itself. Fixes #23293 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y3eKPoHZwqK4AP3GQBSgEG
A successful deletion emits OnTaxonomyChanged with causeOfChange REMOVE_TERM and a failed one emitted DELETE_TERM, which no screen subscribes to. CategoryDetailViewModel, CategoriesListViewModel and SiteSettingsTagListActivity all match on REMOVE_TERM only, so a failure reached nobody and the "Deleting category" progress state stayed on screen indefinitely. Emit both outcomes under REMOVE_TERM. CategoryDetailViewModel already passes event.isError to showDeleteStatusMessage(), so the dialog now resolves to "Failed to delete category" — its tests already build their error callback with REMOVE_TERM, so they were written against this behaviour. This affects every client, not just wp-rs: any failed term deletion left the dialog spinning. Fixes #23293 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y3eKPoHZwqK4AP3GQBSgEG
Both halves reach users in 27.0. The spinner that never resolved was client-agnostic, so every site type was affected regardless of feature flags, and the wrong delete id affected sites on the wp-rs taxonomy path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y3eKPoHZwqK4AP3GQBSgEG
dcalhoun
force-pushed
the
fix/term-deletion-remote-id
branch
from
September 3, 2026 20:13
58ebae5 to
6b41648
Compare
Contributor
|
|
Contributor
|
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## trunk #23294 +/- ##
==========================================
+ Coverage 37.93% 37.95% +0.01%
==========================================
Files 2352 2352
Lines 128925 128925
Branches 17940 17940
==========================================
+ Hits 48907 48928 +21
+ Misses 76012 75994 -18
+ Partials 4006 4003 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
A failed deletion now arrives under REMOVE_TERM, the cause a successful one already used, and this subscriber matched on the cause alone. A rejected delete therefore triggered a refetch: the list posted Loading and, when the network was unavailable — a likely reason the delete failed in the first place — replaced itself with the no-connection screen. Guard the branch with !event.isError, the guard SiteSettingsTagListActivity already applies to loadTags(). The new tests pin both halves: a successful removal still refetches, and a failed one leaves the list as it was. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pj4FNhM3TFx83R5uDSuYh
REMOVE_TERM shared a switch branch with UPDATE_TERM, which hides the progress dialog and pops the detail fragment unconditionally. Now that a failure arrives under that cause, a rejected tag deletion closed the dialog and popped the screen with nothing but an AppLog line — indistinguishable from a success, while the stale tag stayed in the list. Give REMOVE_TERM its own branch and a toast on failure, matching the message the categories screen already shows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pj4FNhM3TFx83R5uDSuYh
The note claimed a term that can't be deleted says so instead of leaving the screen loading, which held only for categories until the tags screen got its own failure message. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pj4FNhM3TFx83R5uDSuYh
The existing test covers only the error branch, though the contract it protects is that both outcomes arrive under REMOVE_TERM — the subscribers that tell success from failure would break silently if the success branch changed cause or rowsAffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pj4FNhM3TFx83R5uDSuYh
Five delete tests each built the same TermsRequestDeleteResponse by hand, and the two data helpers behind them were identical — the tag one returned category-shaped data, which goes unnoticed because the delete path reads only data.deleted. The remote-id test also stated its response twice: it ran the request for its side effect, discarded the result and returned a separately built Success. doSuspendableAnswer lets the executor's own result flow through, dropping the nested runBlocking and the unchecked cast with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pj4FNhM3TFx83R5uDSuYh
dcalhoun
marked this pull request as ready for review
September 3, 2026 21:14
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.


Description
Fixes #23293. Fix CMM-2401.
Deleting a category over the wordpress-rs path failed with a 404 and left the confirmation dialog spinning on "Deleting category" forever. Two independent defects, both from #22252:
deleteTermidentified the term by its local database id.TermModelcarriesmId, the local@PrimaryKeyrow id, andmRemoteTermId, the id the API knows.deleteTermsentterm.id, so the API was asked to delete a term id belonging to something else and answered404 rest_term_invalid. Every sibling already uses the remote identity —TaxonomyXMLRPCClientsendsgetRemoteTermId(),TaxonomyRestClientuses the slug, andupdateTermin the same class sendsterm.remoteTermId. Creating works because it needs no id. The success path had the mirror image of the slip, passing the local id as the confirmation model'sremoteTermId.A failed deletion was reported under a cause nothing subscribes to. A successful deletion emits
OnTaxonomyChanged(causeOfChange = REMOVE_TERM); a failed one emittedDELETE_TERM.CategoryDetailViewModel,CategoriesListViewModelandSiteSettingsTagListActivityall match onREMOVE_TERMonly, so the failure reached nobody and theInProgressstate was never cleared. Both outcomes now emitREMOVE_TERM, andCategoryDetailViewModelalready passesevent.isErrorthrough to the message. This half affects every client — any failed term deletion left the dialog spinning, including on WordPress.com and XML-RPC sites.Moving failures onto the cause every screen already listens to means the other two subscribers now receive an event they never saw before, so both had to be taught what to do with it:
CategoriesListViewModelmatched on the cause alone, so a rejected deletion triggered a refetch — the list postedLoadingand, when the network was unavailable (a likely reason the deletion failed in the first place), replaced itself with the no-connection screen. It now refetches only on success.SiteSettingsTagListActivityshared oneswitchbranch betweenREMOVE_TERMandUPDATE_TERM, which hides the progress dialog and pops the detail fragment unconditionally. A rejected tag deletion therefore closed the dialog and popped the screen with nothing but anAppLogline, indistinguishable from a success while the stale tag stayed in the list. It now shows a "Deleting tag failed" toast, matching the message the categories screen shows.The existing delete tests stub
WpApiClient.requestwholesale, so the id sent was never observable and two assertions had been written against the wrong value. The new test runs the request against a stubbed executor and asserts the id itself. Every new test here was confirmed to fail without its fix.Testing instructions
Note
The category deletion fix requires a site whose taxonomy requests go through wordpress-rs: added with an application password, with
taxonomies_rest_api_migrationenabled in the dev menu. The failure-reporting fixes apply to every site type.Deleting a category:
DELETErequest uses the same id thePOSTresponse returned (ontrunkit uses a different one and returns 404)Failed category deletion no longer hangs (regression check, reproducible on any site type):
DELETE */categories/*with a 500.Failed tag deletion reports the failure:
DELETE */tags/*with a 500.Regressions:
🤖 Generated with Claude Code
https://claude.ai/code/session_011pj4FNhM3TFx83R5uDSuYh