Skip to content

Restore classification suggestion pill to original position on API failure#772

Open
Intenzi wants to merge 5 commits into
WordPress:developfrom
Intenzi:fix/classification-pill-restoration
Open

Restore classification suggestion pill to original position on API failure#772
Intenzi wants to merge 5 commits into
WordPress:developfrom
Intenzi:fix/classification-pill-restoration

Conversation

@Intenzi

@Intenzi Intenzi commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What?

Closes #771

This PR visually adds back the AI suggested/generated tags/categories pills which fail to be added to the post upon performing the click action that visually removes them. (Content Classification Experiment)

Why?

It is expected that when a tag/category is selected (by clicking on it), if it fails to be added, it should be visible again for re-attempting to add it.
It resolves the visual issue and provides better User Experience.

How?

I have implemented the PR in the following manner:

  • Modified addTermToPost method to return a success boolean flag upon completion.
  • This is then utilised inside handleAccept callback which calls addTermToPost
  • If the API call was not successful i.e. addTermToPost returned false for success, we restore the suggested tag/category back to the suggestions state list via setSuggestions(). It also takes into account the index of the suggestion to try restore at the same position from which the pill was removed.

I have then taken account of an additional edge case:
+Lengthy API calls can lead to the user performing further actions such as Suggest again and Dismiss all, in which case we should not be restoring the pill after API failure is received.

  • This has been implemented by storing a generationCounts variable that stores an integer value for the current "session" of pills displayed.

  • Session here implies the state after clicking on "Suggest again" or "Dismiss all" or the initial "Suggest Categories/Tags" button.

  • I track this state as an integer value which gets incremented upon the above mentioned actions.

  • The old session integer is captured in a closure by the .then() callback as clickGenerationCount variable.

  • Once the addTermToPost method fails it compares the present generationCounts against the old clickGenerationCount to determine whether we have a stale state for adding back the suggestion pill.

  • I have refactored suggestion re-insertion logic into a separate method of insertSuggestionAt() to reduce nested code.

+I have implemented e2e tests that validate the resolution of the same.

  • The tests intercept the /wp/v2/tags/ API calls via page.route() to return a simulated failure.
  • The tests utilise manually deferred API responses to simulate/enact a slow network request that only resolves to failure when the test explicitly triggers it (after the user clicks on Suggest again/Dismiss all for the edge case for example).

Use of AI Tools

AI assistance: Yes
Tool(s): Antigravity
Model(s): Gemini 3.5 Flash (Low)
Used for: Identifying the relevant possible fixes. Implemented the E2E tests using the same. Final implementation decision and tests were reviewed and edited by me.

Testing Instructions

  1. Open the post editor.
  2. Make a new post with atleast 150 words of content.
  3. Open the Post sidebar.
  4. Click Suggest Tags (or Categories) to get suggestions. Wait for suggestions to appear.
  5. Turn off your internet connection or perform a simulation of it via developer console.
  6. Click one of the suggested tag pills.
  7. Result: The pill reappears. When the network request fails, the pill returns to the list and is re-useable.

Screenshots or screencast

Screen.Recording.2026-06-25.at.7.27.14.PM.mov

Changelog Entry

Fixed - Restored tag and category suggestion pills to their original positions if the backend term assignment API fails, resolving stale state race conditions.

Open WordPress Playground Preview

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.71%. Comparing base (dd7d364) to head (ce41be4).

Additional details and impacted files
@@            Coverage Diff             @@
##             develop     #772   +/-   ##
==========================================
  Coverage      76.71%   76.71%           
  Complexity      1828     1828           
==========================================
  Files             87       87           
  Lines           7764     7764           
==========================================
  Hits            5956     5956           
  Misses          1808     1808           
Flag Coverage Δ
unit 76.71% <ø> (ø)

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

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

@Intenzi Intenzi marked this pull request as ready for review June 25, 2026 14:48
@github-actions

Copy link
Copy Markdown

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.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Intenzi <intenzi@git.wordpress.org>

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

@Intenzi

Intenzi commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

I haven't implemented for one edge case, I'd like to discuss on clarification around its approach decisions and whether we want to address it at all:

Edge case: Restoring pills when the API call fails after the component has unmounted.

If a user clicks to accept a suggestion (starting the asynchronous addTermToPost call) and immediately switches sidebar tabs (unmounting the classification panel) -- any subsequent API failure will occur when the component is no longer in the DOM. Since the component is unmounted: calling the React state setter setSuggestions() is discarded and the restored pill is lost forever (it is never synced back to the module-level suggestionsCache that was implemented by me in #769 ).

Potential Solution & React Anti-Patterns

To solve this, my thought process was to simply track the mount status using a ref:

  1. Initialize const isMounted = useRef(true) and set it to false in a useEffect cleanup.
  2. In the .then() handler, if isMounted.current is false, update the module-level suggestionsCache[taxonomy] instead of calling setSuggestions() while keeping the setSuggestion modification as-is for if the component isMounted.

However, using isMounted is widely considered an anti-pattern in modern React. It suggests that the component is trying to manage side-effects and global state synchronization that belong outside of its own lifecycle.

Questions for Discussion

To resolve this cleanly, we might need a more holistic reapproach to how API calls and state are managed, or is the module-level cache enough? Is it acceptable to leave it as-is for now, or is decoupling the state management the right next step for this plugin's architecture?

I am thinking that we could use WordPress Data Store to register a redux store that manages the state of the suggestions per taxonomy. I'm hesitant to however introduce that level of architectural overhead for an edge case fix.

Looking forward to your thoughts on the same.

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.

Suggested Tags/Categories when selected are removed from view irrespective of end result being a failure. (Content Classification exp)

1 participant