8e279300b1726f355767b467bc699685e90d487b
max
  Tue Apr 21 06:27:16 2026 -0700
composite hgTrackUi: fix btn_minus_all to hide parent, remember last vis, refs #37182

Two fixes around composite visibility on hgTrackUi:

1) The global [-] button (btn_minus_all) unchecked all subtracks but left
the composite visibility dropdown unchanged. The earlier fix in
814472876ea covered only the matrix-corner [-] (_matSetMatrixCheckBoxes);
the global button goes through matSubCBsCheck, which is now updated to
the same pattern.

2) [-] / [+] and individual subCB clicks now remember the prior visibility
via a data-last-viz attribute on the dropdown. Hiding stashes the
current value; re-showing restores it (falling back to pack/dense if
nothing saved) instead of always jumping to pack.

Factored into two helpers in hui.js: hideCompositeSaveVis() and an
extended exposeAll(). subCfg.onUserCbChange (individual subCB/matCB
path) delegates to the same helpers so behavior is uniform.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

diff --git src/hg/js/hui.js src/hg/js/hui.js
index 4b3ce46764b..c028d2240a1 100644
--- src/hg/js/hui.js
+++ src/hg/js/hui.js
@@ -77,49 +77,66 @@
         } else {
             $( matCBs ).each( function (i) { matChkBoxNormalize( this, classesHidden ); });
         }
     }
     matSubCBsSelected();
     obj.lastIndex = obj.selectedIndex;
 }
 
 function matSelectViewForSubTracks(obj,view)
 {
     waitOnFunction( _matSelectViewForSubTracks, obj,view);
 }
 
 function exposeAll()
 {
-    // Make main display dropdown show pack if currently hide
+    // Unhide composite vis. Prefers data-last-viz (stashed by hideCompositeSaveVis()
+    // when [-] or the last subtrack were unchecked), then pack, dense, else last option.
     var visDD = normed($("select.visDD"));
     if (visDD) {
         if ($(visDD).prop('selectedIndex') === 0) {
-            if ($(visDD).children('option[value="pack"]').length)
+            var saved = $(visDD).attr("data-last-viz");
+            if (saved && $(visDD).children('option[value="'+saved+'"]').length)
+                $(visDD).val(saved);
+            else if ($(visDD).children('option[value="pack"]').length)
                 $(visDD).val("pack");
             else if ($(visDD).children('option[value="dense"]').length)
                 $(visDD).val("dense");
             else
                 $(visDD).prop('selectedIndex',$(visDD).children('option').length - 1);
 	        $(visDD).trigger("change");// trigger on change code, which may trigger supertrack reshaping
         }                         // and effecting inherited subtrack vis
 
         // If superChild and hidden by supertrack, wierd things go on unless we trigger reshape
         if ($(visDD).hasClass('superChild'))
             visTriggersHiddenSelect(visDD);
     }
 }
 
+function hideCompositeSaveVis()
+{
+    // Set composite vis dropdown to hide, stashing the current value in data-last-viz
+    // so exposeAll() can restore it. We set selectedIndex directly without triggering
+    // 'change', because propagateVis re-checks all subCBs when checkedCount===0.
+    var visDD = normed($("select.visDD"));
+    if (visDD && $(visDD).prop('selectedIndex') !== 0) {
+        $(visDD).attr("data-last-viz", $(visDD).val());
+        $(visDD).prop('selectedIndex', 0);
+        $(visDD).addClass('changed');
+    }
+}
+
 function matSubCbClick(subCB)
 {
 // subCB:onclick  When a subtrack checkbox is clicked, it may result in
 // Clicking/unclicking the corresponding matrix CB.  Also the
 // subtrack may be hidden as a result.
 
     // NOTE: if "subCfg" then 'change' event will update it
     if (isFauxDisabled(subCB,false)) { // disabled subCB is still clickable when "subCfg"
         subCB.checked = true;
         fauxDisable(subCB,false,""); // enable and get rid of message
     }
     matSubCBsetShadow(subCB,false);
     hideOrShowSubtrack(subCB);
     // When subCBs are clicked, 3-state matCBs may need to be set
     var classes = matViewClasses('hidden');
@@ -194,39 +211,32 @@
             subCbs = $( subCbs ).filter("."+arguments[sIx]);  // Successively limit list
     }
     if (state) { // If clicking [+], further limit to only checked ABCs
         var classes = matAbcCBclasses(false);
         subCbs = objsFilterByClasses(subCbs,"not",classes);  // remove unchecked abcCB classes
     }
     $( subCbs ).each( function (i) {
         if (this.checked !== state) {
             this.checked = state;
             matSubCBsetShadow(this,false);
             $(this).trigger("change");  // NOTE: if "subCfg" then 'change' event will update it
         }
     });
     if (state)
         exposeAll();  // Unhide composite vis?
-    else if ($("input.subCB:checked:visible").length === 0) {
-        // Set composite to hide directly, without triggering propagateVis
-        // (which would re-check all subCBs when checkedCount===0).
-        var visDD = normed($("select.visDD"));
-        if (visDD && $(visDD).prop('selectedIndex') !== 0) {
-            $(visDD).prop('selectedIndex', 0);
-            $(visDD).addClass('changed');
-        }
-    }
+    else if ($("input.subCB:checked:visible").length === 0)
+        hideCompositeSaveVis();
     showOrHideSelectedSubtracks();
     matSubCBsSelected();
 
     var tbody = normed($('tbody.sortable'));
     if (tbody)
          $(tbody).removeClass('sorting');
     return true;
 }
 
 function matSetMatrixCheckBoxes(state)
 { // Called exclusively by matrix [+][-] buttons on click
     var tbody = normed($('tbody.sortable'));
     if (tbody)
          $(tbody).addClass('sorting');
 
@@ -281,30 +291,35 @@
             matXY = $( matXY ).filter(":checked");
             if (matXY.length === 0)
                 subCBs.each( function (i) { matSubCBcheckOne(this,state); });
             for (var mIx=0;mIx<matXY.length;mIx++) {
                 classes = $(matXY[mIx]).attr("class").split(' ');
                 classes = aryRemove(classes,["matCB","changed","disabled"]);
                 /* jshint loopfunc: true */// function inside loop works and replacement is awkward.
                 $( subCBs ).filter('.'+classes.join(".")).each( function (i) { 
                                                                    matSubCBcheckOne(this,state); 
                                                             });
             }
         }
     } else  // state not checked so no filtering by other matCBs needed
         subCBs.each( function (i) { matSubCBcheckOne(this,state); });
 
+    if (state)
+        exposeAll();  // Unhide composite vis?
+    else if ($("input.subCB:checked:visible").length === 0)
+        hideCompositeSaveVis();
+
     return true;
 }
 
 function matSubCBsEnable(state)
 {   // Enables/Disables subtracks checkboxes.
     // If additional arguments are passed in, the list of CBs will be narrowed by the classes
     var subCBs = $("input.subCB");
     for (var vIx=1;vIx<arguments.length;vIx++) {
         if (arguments[vIx].length > 0)
             subCBs = subCBs.filter("."+arguments[vIx]);  // Successively limit list
     }
     subCBs.each( function (i) {
         if (state) {
             fauxDisable(this,false,'');
             $(this).parent().attr('cursor','');