d121edc0bd2809f1d6de6185a497e6a288958479
braney
  Tue May 12 09:18:29 2026 -0700
hgConvert quickLift: skip pre-lifted tracks, append-and-merge hub file, per-track remove UI, refs #37535

In hgConvert / trackHubBuild:
- Skip tracks that already came from a quickLift hub (quickLiftUrl / quickLifted setting) so they don't get re-lifted to a new destination.
- Append new track stanzas to an existing per-source hub file instead of overwriting it; new stanzas get priorities after the existing max; duplicate track names are skipped. Also avoids re-emitting a parent supertrack that's already in the file.

New public quickLiftHubRemoveTrack(cart, sourceDb, trackName) in trackHub.c rewrites the per-source hub file with the named stanza removed plus all descendant stanzas (parent reference cascade, transitive).

hgTrackUi: adds a "Remove from QuickLift" link next to "Duplicate track" for any tdb carrying a quickLiftDb setting. The link hits hgTrackUi_op=quickLiftRemove which calls quickLiftHubRemoveTrack, hides the track in the cart, and 302s to hgTracks. The op argument cart var is qlSourceDb (renamed from quickLiftSourceDb to avoid colliding with the quickLift.* prefix used elsewhere; values cloned out of the cart hash before cartRemove so the helper doesn't see freed strings).

hgTracks: adds a small "x" icon (printQuickLiftDelIcon) on tracks in a quickLift group, suppressed on the synthetic bigQuickLiftChain track. JS onQuickLiftDelIconClick fires the same hgTrackUi_op endpoint via synchronous XHR and removes every TD whose icon matches the deleted data-track, so the row goes away in both the QuickLift group and the Visible Tracks group.

hubConnect cart handling fixes shaken out by the above:
- hubConnectRemakeTrackHubVar's cart-var prefix is now "quickLift." (with the trailing dot) instead of "quickLift", so unrelated keys like qlSourceDb no longer get parsed as hubId/db and crash cart loading on every CGI. Also skips entries whose hubStatus lookup returned NULL.
- hubConnectStatusListFromCart no longer calls removeQuickListReference when the current db isn't the lift's destination. A side trip to another assembly between two lifts to the same destination was deleting the earlier attachment's cart var; just skip attaching this load and leave the cart alone so the lift survives the round trip.

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

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index 9a055a1b35c..09ad270f6c7 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -1,27 +1,28 @@
 /* Copyright (C) 2014 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "linefile.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "jksql.h"
 #include "jsHelper.h"
 #include "trackDb.h"
 #include "hgTrackUi.h"
+#include "quickLift.h"
 #include "hdb.h"
 #include "hCommon.h"
 #include "hui.h"
 #include "fileUi.h"
 #include "ldUi.h"
 #include "snpUi.h"
 #include "snp125Ui.h"
 #include "snp125.h"
 #include "sample.h"
 #include "wiggle.h"
 #include "hgMaf.h"
 #include "obscure.h"
 #include "chainCart.h"
 #include "chainDb.h"
 #include "gvUi.h"
@@ -3996,30 +3997,38 @@
 	/* Offer to dupe the non-containery tracks including composite and supertrack elements */
 	if (tdbIsDupable(tdb))
 	    {
 	    printf("\n&nbsp;&nbsp;<a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=dupe' >Duplicate track</a>\n", 
 		hgTrackUiName(), cartSessionVarName(), cartSessionId(cart),
 		chromosome, cgiEncode(tdb->track));
 	    if (isDupTrack(tdb->track))
 		{
 		/* Offer to undupe */
 		printf("\n&nbsp;&nbsp;<a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=undupe' >Remove duplicate</a>\n", 
 		    hgTrackUiName(), cartSessionVarName(), cartSessionId(cart),
 		    chromosome, cgiEncode(tdb->track));
 		}
 
 	    }
+	/* Offer to remove tracks coming from a quickLift hub. */
+	char *quickLiftSourceDb = trackDbSetting(tdb, "quickLiftDb");
+	if (quickLiftSourceDb != NULL)
+	    {
+	    printf("\n&nbsp;&nbsp;<a href='%s?%s=%s&c=%s&g=%s&hgTrackUi_op=quickLiftRemove&qlSourceDb=%s' >Remove from QuickLift</a>\n",
+		hgTrackUiName(), cartSessionVarName(), cartSessionId(cart),
+		chromosome, cgiEncode(tdb->track), cgiEncode(quickLiftSourceDb));
+	    }
 	}
 
     if (ct)
         {
         puts("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
         cgiMakeButton(CT_DO_REMOVE_VAR, "Remove custom track");
         cgiMakeHiddenVar(CT_SELECTED_TABLE_VAR, tdb->track);
         puts("&nbsp;");
         if (differentString(tdb->type, "chromGraph"))
             {
             char buf[256];
             if (ajax)
                 // reference to a separate form doesn't work in modal dialog,
                 // so change window.location directly.
                 safef(buf, sizeof(buf), "window.location='%s?hgsid=%s&%s=%s';return false;",
@@ -4381,31 +4390,49 @@
 puts(content);
 freeMem(content);
 freeMem(fileUrl);
 }
 
 void doMiddle(struct cart *theCart)
 /* Write body of web page. */
 {
 boolean isFileFetch = isNotEmpty(cartOptionalString(theCart, "fileUrl"));
 
 if (isFileFetch)
     {
     handleFileFetch(theCart);  // file fetch workaround for CORS issues
     return;
     }
-else
+
+char *earlyOp = cartOptionalString(theCart, "hgTrackUi_op");
+if (earlyOp != NULL && sameString(earlyOp, "quickLiftRemove"))
+    {
+    char *opTrack = cloneString(cartOptionalString(theCart, "g"));
+    char *opSourceDb = cloneString(cartOptionalString(theCart, "qlSourceDb"));
+    char *opDb = cloneString(cartUsualString(theCart, "db", ""));
+    cartRemove(theCart, "hgTrackUi_op");
+    cartRemove(theCart, "qlSourceDb");
+    if (opTrack != NULL && opSourceDb != NULL)
+        {
+        quickLiftHubRemoveTrack(theCart, opSourceDb, opTrack);
+        cartSetString(theCart, opTrack, "hide");
+        }
+    printf("Location: %s?db=%s&%s\r\n\r\n",
+           hgTracksName(), opDb, cartSidUrlString(theCart));
+    return;
+    }
+
 cartWriteHeaderAndCont(theCart, NULL, NULL); // "normal" hgTrackUi
 
 struct trackDb *tdbList = NULL;
 struct trackDb *tdb = NULL;
 char *track;
 struct customTrack *ct = NULL, *ctList = NULL;
 char *ignored;
 
 /* used to have hgBotDelayFrac(0.25) here, replaced with earlyBotCheck()
  * at the beginning of main() to output message here if in delay time
  * 2021-06-21 - Hiram
  */
 if (issueBotWarning)
     {
     char *ip = getenv("REMOTE_ADDR");