e782dd6b9d73b9e138d57e3ad213bb6402b3802d
max
  Wed Aug 26 02:52:41 2026 -0700
hgTracks: deleting a custom track now clears it from the Visible Tracks list too

A visible custom track is listed twice under the image, once in its own group
and once in the Visible Tracks group.  Both the right-click Delete Custom Track
item and the trash icon left the other copy behind until the page was reloaded.
Remove every track-list cell that carries the track's delete icon, in
deleteCustomTrack() so both paths get it.  refs #38087

diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index a45463f4d1d..57c021b4b6b 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -5555,56 +5555,68 @@
         imageV2.drawHighlights();
     }
 }
 
 function deleteCustomTrack (trackName) {
     /* Tell hgCustom to delete the given custom track. Shared by the trash icon
      * and the right-click context menu so there is only one deletion code path. */
     // https://genome.ucsc.edu/cgi-bin/hgCustom?hgsid=1645697744_i0Yp2Di71NytSDdb6r0vUbupIvKO&hgct_do_delete=delete&hgct_del_ct_UserTrack_3545=on
     var hgsid = getHgsid();
     var url = 'hgCustom?hgsid='+hgsid+'&hgct_do_delete=delete&hgct_del_'+trackName+'=on';
     var xhttp = new XMLHttpRequest();
     // this cannot be asyncronous, as users can click quickly here and the hgCustom calls above cannot run in parallel
     // since we store custom tracks as a text file, not mysql tables
     xhttp.open("GET", url, false);
     xhttp.send();
+    removeCustomTrackCells(trackName);
+}
+
+function removeCustomTrackCells (trackName) {
+    /* Remove this custom track from the track list under the image. A visible track is
+     * listed twice, once in its own group and once in the Visible Tracks group, so drop
+     * every cell that carries its delete icon, not just the one that was clicked. */
+    var selector = 'div.trackDeleteIcon[data-track="' + trackName + '"]';
+    document.querySelectorAll(selector).forEach(function(d) {
+        var td = d.closest("td");
+        if (td)
+            td.remove();
+    });
 }
 
 function deleteAllBlatTracks () {
     /* Tell hgCustom to delete every BLAT result custom track at once (those tagged blatResult=on),
      * then reload so the BLAT Results group updates. Triggered by the group's "Delete all" button. */
     var hgsid = getHgsid();
     var url = 'hgCustom?hgsid='+hgsid+'&hgct_do_delete_blat=1';
     var xhttp = new XMLHttpRequest();
     // synchronous, for the same reason as deleteCustomTrack: custom tracks live in a text file, not
     // in parallel-safe mysql rows
     xhttp.open("GET", url, false);
     xhttp.send();
     window.location.reload();
 }
 
 function onTrackDelIconClick (ev) {
     /* delete custom track if user clicks its trash icon */
     var divEl = ev.target.closest("div"); // must use .closest(), as user can click on either the SVG or the DIV space.
     var trackName = divEl.getAttribute("data-track");
     deleteCustomTrack(trackName);
     // if the track is currently drawn, also remove it from the image and update
     // hgTracks.trackDb/cart, the same cleanup the right-click delete does. hideTracks()
     // assumes the track is in hgTracks.trackDb, so only call it when it is.
     if (hgTracks.trackDb && hgTracks.trackDb[trackName])
         rightClick.hideTracks([trackName]);
-    divEl.closest("td").remove();
 }
 
 function onQuickLiftDelIconClick (ev) {
     /* Remove this track's stanza from the quickLift hub in trash, and
      * remove all rows referencing it from the page (it can appear both in
      * its hub group and in the Visible Tracks group). */
     var divEl = ev.target.closest("div");
     var trackName = divEl.getAttribute("data-track");
     var sourceDb = divEl.getAttribute("data-sourcedb");
     var hgsid = getHgsid();
     var url = 'hgTrackUi?hgsid=' + hgsid +
               '&hgTrackUi_op=quickLiftRemove' +
               '&g=' + encodeURIComponent(trackName) +
               '&qlSourceDb=' + encodeURIComponent(sourceDb);
     var xhttp = new XMLHttpRequest();