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/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c
index 160024cb0b9..b236e3f45bc 100644
--- src/hg/hgCustom/hgCustom.c
+++ src/hg/hgCustom/hgCustom.c
@@ -65,30 +65,31 @@
 #define hgCtUpdatedTable  CT_UPDATED_TABLE_VAR
 
 /* misc */
 #define hgCtUpdatedTrack "hgct_updatedTrack"
 #define hgCtDeletePrefix "hgct_del"
 #define hgCtRefreshPrefix "hgct_refresh"
 #define hgCtConfigLines   "hgct_configLines"
 #define hgCtNavDest 	 hgCtNoRemove "navDest"
 
 /* commands */
 #define hgCtDo		  hgCt   "do_"	  /* prefix for all commands */
 #define hgCtDoAdd	  hgCtDo "add"
 #define hgCtDoDelete	  hgCtDo "delete"
 #define hgCtDoDeleteSet	  hgCtDo "delete_set"
 #define hgCtDoDeleteClr	  hgCtDo "delete_clr"
+#define hgCtDoDeleteBlat  hgCtDo "delete_blat"	/* delete all BLAT result tracks at once */
 #define hgCtDoRefresh     hgCtDo "refresh"
 #define hgCtDoRefreshSet  hgCtDo "refresh_set"
 #define hgCtDoRefreshClr  hgCtDo "refresh_clr"
 #define hgCtDoGenomeBrowser	  hgCtDo "gb"
 #define hgCtDoTableBrowser	  hgCtDo "tb"
 #ifdef PROGRESS_METER
 #define hgCtDoProgress	  hgCtDo "progress"
 #endif
 
 /* Global variables */
 struct cart *cart;
 struct hash *oldVars = NULL;
 char *excludeVars[] = {"Submit", "submit", "SubmitFile", "ContinueWithWarn", NULL};
 char *database = NULL;
 char *organism = NULL;
@@ -1081,30 +1082,47 @@
 for (ct = ctList; ct != NULL; ct = ct->next)
     {
     char var[256];
     safef(var, sizeof var, "%s_%s", hgCtDeletePrefix, ct->tdb->track);
     if (!cartUsualBoolean(cart, var, FALSE))
         continue;
     /* myVariants tracks are backed by a per-user SQL table, not the CT
      * file, so dropping them from ctList alone isn't enough -- the next
      * page load regenerates the CT entry from the database.  Delegate
      * the type-specific cleanup. */
     myVariantsHandleCtRemoval(ct, cart, database);
     slRemoveEl(&ctList, ct);
     }
 }
 
+void doDeleteBlatCustom()
+/* remove all BLAT result custom tracks at once (those tagged blatResult=on). This backs the
+ * "Delete all" button in hgTracks' BLAT Results group, so users are not left removing accumulated
+ * BLAT results one at a time. */
+{
+struct customTrack *ct, *next;
+for (ct = ctList; ct != NULL; ct = next)
+    {
+    next = ct->next;
+    if (ct->tdb != NULL && sameOk(trackDbSetting(ct->tdb, "blatResult"), "on"))
+        {
+        myVariantsHandleCtRemoval(ct, cart, database);
+        slRemoveEl(&ctList, ct);
+        }
+    }
+}
+
 void doRefreshCustom(char **warnMsg)
 /* reparse custom tracks from URLs based on cart variables */
 {
 struct customTrack *ct;
 struct customTrack *replacedCts = NULL;
 struct customTrack *refreshCts = NULL;
 
 for (ct = ctList; ct != NULL; ct = ct->next)
     {
     char var[256];
     safef(var, sizeof var, "%s_%s", hgCtRefreshPrefix, ct->tdb->track);
     if (cartUsualBoolean(cart, var, FALSE))
 	{
 	struct customTrack *nextCt = NULL, *urlCt = NULL;
 	struct customTrack *urlCts =
@@ -1477,30 +1495,35 @@
         if ((selectedTable= cartOptionalString(cart, hgCtUpdatedTable)) != NULL)
             {
             ct = ctFromList(ctList, selectedTable);
             doUpdateCustom(ct, err, warnOnly);
             }
         else
             doAddCustom(err, warnOnly);
        	cartRemovePrefix(cart, hgCt);
 	return;
 	}
     if (cartVarExists(cart, hgCtDoDelete))
         {
 	doDeleteCustom();
         ctUpdated = TRUE;
         }
+    if (cartVarExists(cart, hgCtDoDeleteBlat))
+        {
+	doDeleteBlatCustom();
+        ctUpdated = TRUE;
+        }
     if (cartVarExists(cart, hgCtDoRefresh))
 	{
 	doRefreshCustom(&warnMsg);
 	addWarning(dsWarn, warnMsg);
         ctUpdated = TRUE;
 	}
     if (ctUpdated || ctConfigUpdate(ctFileName))
 	{
 	customTracksSaveCart(database, cart, ctList);
 
 	/* refresh ctList again to pickup remote resource error state */
 	warnOnly=FALSE;
 	struct errCatch *catch = errCatchNew();
 	if (errCatchStart(catch))
 	    ctList = customTracksParseCartDetailed(database, cart, &browserLines, &ctFileName,
@@ -1509,31 +1532,31 @@
 	if (catch->gotError)
 	    {
 	    addWarning(dsWarn, err);
 	    addWarning(dsWarn, catch->message->string);
 	    }
 	errCatchFree(&catch);
 	}
     warnMsg = dyStringCannibalize(&dsWarn);
     if (measureTiming)
 	{
 	long lastTime = clock1000();
 	loadTime = lastTime - thisTime;
 	}
 
 
-    if (ctList || cartVarExists(cart, hgCtDoDelete))
+    if (ctList || cartVarExists(cart, hgCtDoDelete) || cartVarExists(cart, hgCtDoDeleteBlat))
         doManageCustom(warnMsg);
     else
 	doAddCustom(warnMsg, warnOnly);
     }
 cartRemovePrefix(cart, hgCt);
 cartRemove(cart, CT_CUSTOM_TEXT_VAR);
 }
 
 
 int main(int argc, char *argv[])
 /* Process command line. */
 {
 long enteredMainTime = clock1000();
 issueBotWarning = earlyBotCheck(enteredMainTime, "hgCustom", delayFraction, 0, 0, "html");
 htmlPushEarlyHandlers();