Skip to content

Fix/25020 tag disappears dropdown#25262

Merged
9larsons merged 4 commits into
TryGhost:mainfrom
abdultalha0862:fix/25020-tag-disappears-dropdown
Jun 11, 2026
Merged

Fix/25020 tag disappears dropdown#25262
9larsons merged 4 commits into
TryGhost:mainfrom
abdultalha0862:fix/25020-tag-disappears-dropdown

Conversation

@abdultalha0862

Copy link
Copy Markdown
Contributor

Description

Brief summary of changes:

Fixed tag disappearing from the dropdown after removal in the post editor. When a tag was removed while the tags dropdown was open, it would disappear from the available options until the editor was closed and reopened. This is now fixed by comparing tag IDs instead of object references.

Related issue(s):

Fixes #25020

Type of change:

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality not to work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • UI/UX improvement

Testing

How has this been tested?

  • Manual testing
  • Unit tests
  • Integration tests
  • End-to-end tests

Test environment:

  • Ghost version: 6.4.0 (development)
  • Node.js version: v22.19.0
  • Database (MySQL/SQLite): SQLite
  • Browser (if applicable): Chrome/Firefox (tested in development mode)

Manual Testing Steps:

  1. Navigate to Posts → New Post (or edit existing post)
  2. Click Settings (⚙️) in top right
  3. Scroll to Tags section
  4. Click in tags input to open dropdown
  5. Select a tag from dropdown to add it
  6. While keeping dropdown open, click X to remove the tag
  7. Result: Tag immediately reappears in dropdown (previously it would disappear)

Screenshots (if applicable)

Before:
Tag would disappear from the

Issue_After_Fix_25020.mp4

dropdown after removal and not reappear until editor was closed/reopened.

After:
Tag immediately reappears in dropdown after removal, maintaining consistency with expected behavior.

Checklist

Code Quality:

  • My code follows Ghost's coding standards
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have added JSDoc comments for new functions/methods

Testing:

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested this change across different browsers (if UI-related)
  • I have tested this change with different Ghost configurations

Documentation:

  • I have made corresponding changes to the documentation
  • I have updated relevant code comments
  • Any breaking changes are documented

Ghost-Specific:

  • I have tested this with both MySQL and SQLite databases
  • Changes work correctly in both development and production modes
  • I have considered the impact on existing Ghost installations
  • Admin UI changes are responsive and accessible
  • Theme changes are backward compatible (if applicable)

Additional Notes

Root Cause Analysis:

The availableTags getter in gh-tags-token-input.js was using a JavaScript Set to filter out selected tags by storing tag objects directly. However, JavaScript Sets use reference equality for objects, not value equality. When a tag is loaded from the initial tags list versus when it's selected/removed from a post, these are different object instances even though they represent the same logical tag. This caused Set.has(tag) to return false for removed tags, incorrectly excluding them from the available options.

Solution:

Changed the Set to store tag IDs (strings/numbers) instead of tag objects. IDs are primitive values that use value equality, ensuring consistent filtering regardless of when or how the tag object was created.

Code change:

// Before (buggy)
const selectedSet = new Set(selectedTags);
return this._initialTags.filter(tag => !selectedSet.has(tag));

// After (fixed)
const selectedTagIds = new Set(selectedTags.map(tag => tag.id));
return this._initialTags.filter(tag => !selectedTagIds.has(tag.id));

https://github.com/user-attachments/assets/932dccb6-db88-4809-9b0

https://github.com/user-attachments/assets/fa16115d-9d19-493a-8fe9-80305ad7e37c

7-cfc93249eee8


https://github.com/user-attachments/assets/bb330d90-1a99-496e-b6c1-5728c2874aca

@cursor

cursor Bot commented Oct 27, 2025

Copy link
Copy Markdown

You have run out of free Bugbot PR reviews for this billing cycle. This will reset on November 3.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@github-actions github-actions Bot added the community [triage] Community features and bugs label Oct 27, 2025
@coderabbitai

coderabbitai Bot commented Oct 27, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Changes to the tag input component modify how tags are managed and deduplicated in the dropdown. The constructor no longer pre-adds selected tags to initial tags. Available tags are now filtered using ID-based comparison instead of object reference matching. The addInitialTags and addSearchedTags methods each maintain a Set to track existing tags and prevent duplicates when accumulating tags. These changes ensure selected tags remain available for re-selection and that duplicates are avoided when building the available tag list.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Logic flow verification: Confirm that ID-based filtering correctly identifies and excludes selected tags from the available list across all interaction patterns
  • Deduplication correctness: Verify that Set-based deduplication in addInitialTags and addSearchedTags works correctly when tags are added incrementally and when the dropdown is opened/closed
  • Selected tag retention: Ensure that removing and re-adding tags to the post allows tags to appear in the dropdown as expected
  • Edge case handling: Test scenarios where tags are added without expanding the dropdown first versus after expansion

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Fix/25020 tag disappears dropdown" directly describes the primary change in the changeset. The title clearly indicates this is a fix for issue #25020 related to tags disappearing from a dropdown interface. While slightly informal in phrasing, it is sufficiently specific and concise for a developer scanning the history to understand that this resolves a tag visibility issue in a dropdown component. The title aligns with the actual code changes targeting the gh-tags-token-input.js file.
Linked Issues Check ✅ Passed The code changes address the core requirements of issue #25020. The issue requires that tags removed while the dropdown is open should remain available in the dropdown for re-selection, with all unlinked tags always appearing unless already linked to the post. The PR implements this by changing from object reference-based filtering to ID-based filtering in the availableTags getter and improving deduplication logic in addInitialTags and addSearchedTags. This change ensures that different object instances representing the same tag (as identified by ID) are correctly recognized and handled consistently, resolving the root cause of the disappearing tag bug for existing saved or published posts.
Out of Scope Changes Check ✅ Passed All code modifications in this pull request are directly scoped to addressing the linked issue #25020. The changes include: modifying the constructor to avoid pre-adding selected tags, implementing ID-based filtering in the availableTags getter, and improving deduplication logic in addInitialTags and addSearchedTags. These modifications collectively target the root cause (object reference equality) and implement the necessary fix (ID-based comparison and proper tag management). No unrelated features, refactoring, or other scope creep is evident in the changeset.
Description Check ✅ Passed The pull request description is detailed and directly related to the changeset. It provides a clear summary of the bug fix (tags disappearing from dropdown when removed while dropdown is open), references the linked issue #25020, explains the root cause (object reference equality vs. value equality in Sets), describes the solution (id-based comparison), includes manual testing steps performed in a development environment, and documents the code changes. The description is substantive and relevant to the implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
ghost/admin/app/components/gh-tags-token-input.js (2)

48-51: Consider ID-based deduplication for consistency.

While object reference equality likely works with Ember Data's identity map, using ID-based deduplication would be more consistent with the availableTags fix and more robust against edge cases.

Apply this diff for ID-based deduplication:

-    // Store all tags, but avoid duplicates
-    const existingTags = new Set(this._initialTags);
-    const newTags = tags.filter(tag => !existingTags.has(tag));
-    this._initialTags.push(...newTags);
+    // Store all tags, but avoid duplicates
+    const existingTagIds = new Set(this._initialTags.map(tag => tag.id));
+    const newTags = tags.filter(tag => !existingTagIds.has(tag.id));
+    this._initialTags.push(...newTags);

56-59: Consider ID-based deduplication for consistency.

Same recommendation as addInitialTags - ID-based deduplication would be more consistent with the availableTags fix.

Apply this diff for ID-based deduplication:

-    // Store all search results, but avoid duplicates 
-    const existingTags = new Set(this._searchedTags);
-    const newTags = tags.filter(tag => !existingTags.has(tag));
-    this._searchedTags.push(...newTags);
+    // Store all search results, but avoid duplicates
+    const existingTagIds = new Set(this._searchedTags.map(tag => tag.id));
+    const newTags = tags.filter(tag => !existingTagIds.has(tag.id));
+    this._searchedTags.push(...newTags);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d99fb48 and 3c521a8.

📒 Files selected for processing (1)
  • ghost/admin/app/components/gh-tags-token-input.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ghost/admin/app/components/gh-tags-token-input.js (1)
ghost/admin/tests/integration/components/gh-psm-tags-input-test.js (3)
  • tags (17-17)
  • tags (223-223)
  • tags (246-246)
🔇 Additional comments (2)
ghost/admin/app/components/gh-tags-token-input.js (2)

27-28: Good clarification of the new tag initialization behavior.

The comment clearly explains that selected tags are no longer pre-added to _initialTags, allowing the ID-based filtering in availableTags to work correctly when tags are removed and should reappear in the dropdown.


33-34: Verify selectedTags management after tag save—ID-based filtering may fail for newly created tags.

The fix correctly resolves the reference equality bug by using primitive IDs. However, Ember Data replaces temporary IDs with server-provided IDs when saving newly created records. If newly created tags are added to selectedTags with temporary IDs and then saved, the selectedTagIds Set will contain outdated temporary IDs while the tag objects themselves receive server IDs. This causes the filter to incorrectly include recently-saved tags in availableTags.

Action required:

  • Verify how the parent component manages selectedTags after saving the post/page
  • Confirm whether selectedTags is refreshed with server-loaded tags (correct) or maintains references to pre-save objects (buggy)
  • If the latter, consider using tag names or persisting client-generated IDs instead of relying on Ember Data's auto-assigned IDs

abdultalha0862 and others added 4 commits June 10, 2026 16:43
fixes TryGhost#25020
Tag selector was excluding deleted tags permanently by checking against
selectedTags Set without filtering out removed tags first. This prevented
users from re-selecting tags they had previously removed from a post.
Optimized deduplication logic to use Set.has() instead of Array.includes()
across all methods for consistent O(1) performance. Fixed root cause where
constructor was incorrectly filtering selected tags from initial tag pool,
preventing re-selection after removal. Now stores all available tags and
filters only in availableTags getter as intended.
fixes TryGhost#25020

When editing a post, removing a tag while the dropdown was open would cause the tag to disappear from available options. This happened because the Set was comparing object references instead of tag IDs. Tags loaded at different times create different object instances even for the same logical tag, causing Set.has() to fail. Fixed by comparing tag IDs which remain constant regardless of when/how the tag object was created.
ref TryGhost#25020

- restored filtering of already-selected tags from server-side search results, now comparing by tag id instead of object reference
- restored seeding of the initial tags list with the selected tags so a tag from beyond the first loaded page reappears in the dropdown after removal; seeding now uses the TrackedArray constructor to avoid a render-time mutation deprecation
- removed code comments referencing the issue
- added integration tests covering tag re-appearance after removal and selected-tag exclusion from server-side search results
@9larsons 9larsons force-pushed the fix/25020-tag-disappears-dropdown branch from 2690e99 to d9d75b9 Compare June 10, 2026 21:55
Copilot AI review requested due to automatic review settings June 10, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes a post-editor tag dropdown bug where removing a selected tag while the dropdown is open could cause the tag to disappear from the available options until the editor is reopened. The fix updates tag filtering/deduplication logic to compare by tag IDs instead of object reference equality.

Changes:

  • Initialize _initialTags with the currently selected tags and filter available options by tag.id rather than object identity.
  • Deduplicate loaded/queried tags using tag IDs for both initial-load and server-side search results.
  • Add integration coverage for (a) removed tags reappearing in the dropdown and (b) excluding selected tags from server-side search results.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
ghost/admin/app/components/gh-tags-token-input.js Switches selection filtering/deduplication from reference equality to ID equality to keep removed tags available immediately.
ghost/admin/tests/integration/components/gh-psm-tags-input-test.js Adds integration tests for the regression scenario and for server-side search excluding selected tags.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ghost/admin/tests/integration/components/gh-psm-tags-input-test.js

@9larsons 9larsons left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice find and change! LGTM.

@9larsons 9larsons enabled auto-merge (squash) June 11, 2026 15:33
@9larsons 9larsons merged commit 727e6f4 into TryGhost:main Jun 11, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community [triage] Community features and bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Editor - Tag disappears from drop-down when deleting from post

3 participants