Merged
Conversation
akatsoulas
approved these changes
Apr 17, 2026
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.
mozilla/sumo#2965
Here's the explanation from
claude:How the sidebar works
The sidebar tag filter (
#sidebar-tag-filter) loads its content lazily viahx-trigger="load". Once loaded,initSidebarTagFilter()runs, queries the DOM for the button and tag items, attaches a click listener to.sidebar-tags--show-more, and setscontainer.dataset.initialized = "true"as a guard against duplicate initialization.What happens when you click a tag
Each tag link has
hx-push-url="true", which matters because of howhtmx1.9.12 orders operations internally. Looking at thehtmxsource (line 3618),htmx:pushedIntoHistoryfires beforeselectAndSwap— meaning the URL is pushed before the question list is actually swapped.The sequence:
htmx:pushedIntoHistoryfires — Our handler constructs a new sidebar URL and callshtmx.ajax("GET", newUrl, {target: sidebar, swap: "innerHTML" }). This starts an async request to reload the sidebar.htmx:afterSettlewhereevent.detail.targetis#questions-list..sidebar-tags--show-morebutton (and its click listener) is destroyed. A new button exists in the DOM but has no listener.htmx:afterSettlefires —event.detail.targetis the sidebar element.The bug
The old code at step 4:
This calls
initSidebarTagFilter(), which checkscontainer.dataset.initialized. It's still "true" (set on the container element, which was never replaced — only its children were). So the function returns early at line 346. The new button never gets a listener.The
.then()callback onhtmx.ajax()was supposed to clear the flag and re-initialize. Buthtmx1.9.12 has a default settle delay of 20ms (setTimeout(doSettle, 20)) while the promise resolves synchronously in the XHRonloadhandler (line 3351). In the simple case this actually works (.then()runs beforesettle). But when requests get queued — e.g., the user clicks tags rapidly —htmx's sync strategy resolves the promise immediately at line 3166 without performing the swap. The.then()then re-initializes against stale DOM, and when the real swap finally happens, the listeners are destroyed with no recovery path.The fix
Change 1 — Make the
htmx:afterSettlehandler aware of what was swapped:When a question-list swap settles, target is
#questions-list, so the flag stays put andinitSidebarTagFilter()correctly returns early. When the sidebar swap settles, target is the sidebar itself, so we clear the flag andinitSidebarTagFilter()runs fresh — querying the new DOM, attaching a listener to the new button.Change 2 — Remove the
.then()since thehtmx:afterSettlehandler now reliably handles re-initialization regardless of promise timing or request queuing.