f48fc325d72424177878c4849096c77e06c2d3b0
max
  Mon Aug 17 07:34:42 2026 -0700
BLAT results: own track group, Delete-all button, clearer names, refs #38086

Behind hg.conf blatResultsGroup (default off, a release gate):

- BLAT result custom tracks go into their own "BLAT Results" track group
instead of the generic Custom Tracks group.
- That group's header gets a "Delete all" button that removes every BLAT
result track at once, so users are not stuck deleting them one by one.
- Headerless queries are named by query size + the top hit's gene, e.g.
"360bp SOD1", instead of the useless "blat YourSeq"; the date goes in
the longLabel.

hgBlat.c       getCustomName naming + blatDateStamp/topHitLocusLabel helpers
hgc.c          buildBigPsl tags the results track group=blat
customFactory.c checkGroup accepts the synthetic "blat" group
hgTracks.c     synthesize the BLAT Results group; Delete-all button; keep
the per-track delete icon for the new group
hgTracks.js    deleteAllBlatTracks()
hgCustom.c     hgct_do_delete_blat action (removes all blatResult=on tracks)
hgConfCatalog.py  register blatResultsGroup as a release gate

diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c
index 45051e58b9a..6f695dd6b82 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -7241,30 +7241,47 @@
 //
 // If there isn't a map group, make one and set the priority so it will be right after custom tracks
 // and hub groups.
 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.
+if (cfgOptionBooleanDefault("blatResultsGroup", FALSE))
+    {
+    AllocVar(group);
+    group->name = cloneString("blat");
+    group->label = cloneString("BLAT Results");
+    group->defaultPriority = group->priority = 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);
 
 
 /* Loop through tracks and fill in their groups.
  * If necessary make up an unknown group. */
 for (track = *pTrackList; track != NULL; track = track->next)
@@ -8840,31 +8857,31 @@
 printTrashIcon("Delete this custom track", "trackDeleteIcon", dataAttrs);
 }
 
 static void printQuickLiftDelIcon(struct track *track, char *sourceDb)
 /* Trash-can icon next to a track in a quickLift group; clicking it removes
  * the track from the quickLift hub. */
 {
 char dataAttrs[1024];
 safef(dataAttrs, sizeof dataAttrs, "data-track='%s' data-sourcedb='%s'", track->track, sourceDb);
 printTrashIcon("Remove this track from the QuickLift group", "quickLiftDelIcon", dataAttrs);
 }
 
 static void printTrackLink(struct track *track)
 /* print a link hgTrackUi with shortLabel and various icons and mouseOvers */
 {
-if (sameOk(track->groupName, "user"))
+if (sameOk(track->groupName, "user") || sameOk(track->groupName, "blat"))
     printTrackDelIcon(track);
 
 char *quickLiftSourceDb = (track->tdb != NULL) ?
         trackDbSetting(track->tdb, "quickLiftDb") : NULL;
 /* skip the synthetic "Alignment Differences" track -- it's added at runtime
  * by hubConnect, not from the hub file, so removing it from the file is a
  * no-op and it would reappear on the next page load. */
 if (quickLiftSourceDb != NULL &&
     (track->tdb->type == NULL || !startsWith("bigQuickLiftChain", track->tdb->type)))
     printQuickLiftDelIcon(track, quickLiftSourceDb);
 
 if (track->hasUi)
     {
     char *url = trackUrl(track->track, chromName);
     char *longLabel = replaceChars(track->longLabel, "\"", """);
@@ -10212,30 +10229,42 @@
                         if (hub->email)
                             hPrintf("The authors can be reached at %s", hub->email);
                         hPrintf("' href='%s' "
                             "style='color:#FFF; font-size: 13px;' target=_blank>Info</a>", hub->descriptionUrl);
                         }
                     hPrintf("&nbsp;&nbsp;");
 
 
                     }
                 }
 
             hPrintf("<button type='button' class=\"hgtButtonHideGroup\" data-group-name=\"%s\" "
                     "title='Hide all tracks in this group'>Hide group</button>&nbsp;",
                     group->name);
 
+            // The BLAT Results group gets a "Delete all" button that removes every BLAT result track
+            // at once, so users are not stuck deleting accumulated results one by one.
+            if (sameString(group->name, "blat"))
+                {
+                safef(idText, sizeof idText, "%s_delAll", group->name);
+                hPrintf("<button type='button' id='%s' "
+                        "title='Delete all BLAT result tracks'>Delete all</button>&nbsp;", idText);
+                jsOnEventByIdF("click", idText,
+                    "if (window.confirm('Delete all %d BLAT result tracks?')) deleteAllBlatTracks();",
+                    slCount(group->trackList));
+                }
+
             if (hub || group->errMessage)
                 {
 		safef(idText, sizeof idText, "%s_%d_disconn", hubName, disconCount);
                 disconCount++;
                 hPrintf("<input name=\"hubDisconnectButton\" title='Disconnect third-party track hub and remove all tracks' "
                         "id='%s'"
                     " type=\"button\" value=\"Disconnect\">\n", idText);
 		jsOnEventByIdF("click", idText,
                     "if (window.confirm(\"Disconnect this hub?\\n\\nReconnecting later will require navigating to My Data -> Track Hubs to find it again or re-entering the hub URL.\")) {"
                     "document.disconnectHubForm.elements['hubId'].value='%s';"
                     "document.disconnectHubForm.submit();return true;"
                     "}",
 		    hubName + strlen(hubTrackPrefix));
 
 #ifdef GRAPH_BUTTON_ON_QUICKLIFT