99880a42ac57bb8b8aff144a9e4401c8a408b27b
max
  Tue Aug 18 07:06:40 2026 -0700
hgTracks: derive BLAT Results group priority from hub increment, refs #38086

The synthetic BLAT Results track group was pinned to a hardcoded
priority of 1.5.  Hub groups, however, are assigned priorities
dynamically starting at 1.0 + priorityInc and spanning up to
1.0 + 0.9*(minPriority-1), so when the smallest real group priority is
high enough (e.g. map=2.0) some hub groups can land above 1.5 and the
BLAT Results group is no longer guaranteed to sit directly below Custom
Tracks -- it can be sandwiched among hub groups.

Derive the group's priority from the same priorityInc the hub loop uses:
place it at 1.0 + priorityInc/2, i.e. between Custom Tracks (1.0) and the
first hub group (1.0 + priorityInc), so it always stays directly below
Custom Tracks no matter how many hubs are connected.  With no hubs, keep
1.5, which sits safely between Custom Tracks and the first real group.

diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c
index c9ef24b90c0..442d61d3c3f 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -7206,32 +7206,33 @@
             cartRemove(cart, cartVar);
         }
     /* create group object; add to list and hash */
     AllocVar(group);
     group->name = cloneString(grp->name);
     group->label = cloneString(grp->label);
     group->defaultPriority = grp->priority;
     group->priority = priority;
     group->defaultIsClosed = grp->defaultIsClosed;
     group->errMessage = grp->errMessage;
     slAddHead(&list, group);
     hashAdd(hash, grp->name, group);
     }
 grpFreeList(&grps);
 
-double priorityInc;
+double priorityInc = 0;
 double priority = 1.00001;
+boolean haveHubs = (grpList != NULL);   // grpList is consumed by the loop below, so capture this now
 if (grpList)
     {
     minPriority -= 1.0;             // priority is 1-based
     // the idea here is to get enough room between priority 1
     // (which is custom tracks) and the group with the next
     // priority number, so that the hub nestle inbetween the
     // custom tracks and everything else at the top of the list
     // of track groups
     priorityInc = (0.9 * minPriority) / slCount(grpList);
     priority = 1.0 + priorityInc;
     }
 for(; grpList; grpList = grpList->next)
     {
     AllocVar(group);
     group->name = cloneString(grpList->name);
@@ -7249,39 +7250,44 @@
 if (!foundMap)
     {
     AllocVar(group);
     group->name = cloneString("map");
     group->label = cloneString("Mapping and Sequencing");
     group->defaultPriority = priority;
     group->priority = priority;
     group->defaultIsClosed = FALSE;
     slAddHead(&list, group);
     hashAdd(hash, "map", group);
     }
 
 // The "BLAT Results" group holds BLAT-search result custom tracks (group=blat, tagged
 // blatResult=on), keeping them out of the generic Custom Tracks group so they are easy to find and
 // clear as a set.  It is synthesized here rather than stored in the grp table; the group header is
-// skipped when it has no tracks (see the group loop that draws the controls).  Priority 1.5 places
-// it just after Custom Tracks (priority 1) so BLAT users find their results near the top.  Gated by
-// hg.conf blatResultsGroup, the same flag hgBlat/hgc read before tagging tracks with group=blat.
+// skipped when it has no tracks (see the group loop that draws the controls).  Gated by hg.conf
+// blatResultsGroup, the same flag hgBlat/hgc read before tagging tracks with group=blat.
 if (cfgOptionBooleanDefault("blatResultsGroup", FALSE))
     {
     AllocVar(group);
     group->name = cloneString("blat");
     group->label = cloneString("BLAT Results");
-    group->defaultPriority = group->priority = 1.5;
+    // Place the group just after Custom Tracks (priority 1) so BLAT users find their results near
+    // the top.  When hubs are attached their groups are spread across (1.0, 1.0 + 0.9*minPriority)
+    // starting at 1.0 + priorityInc, so a hardcoded 1.5 could land in the middle of them; instead
+    // slot BLAT Results at 1.0 + priorityInc/2, i.e. between Custom Tracks and the first hub group,
+    // so it stays directly below Custom Tracks no matter how many hubs are connected.  With no hubs
+    // priorityInc is unset, but 1.5 sits safely between Custom Tracks and the first real group.
+    group->defaultPriority = group->priority = haveHubs ? (1.0 + priorityInc/2) : 1.5;
     group->defaultIsClosed = FALSE;
     slAddHead(&list, group);
     hashAdd(hash, "blat", group);
     }
 
 // The "Visible Tracks" group is now the default top group
 struct group *visible = NULL;
 AllocVar(visible);
 visible->name = "visible";
 visible->label = "Visible Tracks";
 visible->defaultPriority = priority;
 visible->priority = -1;
 visible->defaultIsClosed = FALSE;
 slAddHead(&list, visible);
 hashAdd(hash, "visible", visible);