ee190bc09015bf1d10254691824b0a38f59e1aff
braney
  Fri Sep 4 11:39:56 2026 -0700
hgc: show the lifted alignment on a quickLifted psl or bigPsl details page

The details page and its alignment views all read the assembly on screen, so a
quickLifted alignment track either reported the position it had before the lift
or, for the base alignment views, could not find its item at all.

Finding the track was the first problem.  aliTable is the table name from the
assembly the alignments came from, and that name usually belongs to a real table
on the assembly being viewed as well, so it cannot tell the two apart.  The
alignment links now carry aliTrack, the track hgc was called on, built the same
way hgcAnchorSomewhere builds its table parameter.  A hub track's trackDb only
reaches trackHash if its hub is attached in cartDoMiddle, which was already being
done for the two htcBigPsl commands and now covers the cdna and protein ones too.

quickLiftAliInfo resolves the trackDb, assembly, table and chain file behind an
aliTable and aliTrack pair, custom tracks included.  quickLiftFindPsl finds the
alignment a link points at: only the reference position is known there and the
lift does not run backwards, so it reads every alignment of the accession out of
the other assembly, lifts them, and keeps the one that lands on that position.

htcCdnaAli and htcCdnaAliInWindow use that, and read the CDS and the query
sequence from the source assembly.  htcBigPslAli and htcBigPslAliInWindow read
the source intervals through quickLiftGetIntervals and match on the lifted
position rather than on the raw interval.  Several name tests that decide what
kind of alignment this is now skip the hub prefix, so the guard against a
translated alignment in a window is not bypassed on a lifted xeno track.

Separately, and not specific to quickLift: genericBigPslClick read through an
empty alignment list, which is what happens whenever nothing in the window
matches the item asked for.

refs #38249

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 017daa1bb4e..3896466c7db 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -3456,34 +3456,44 @@
 
 if (start == end)
     {  
     // item is an insertion; expand the search range from 0 bases to 2 so we catch it:
     ivStart = max(0, start-1);
     ivEnd++;
     }  
 
 if (cfgOptionBooleanDefault("drawDot", FALSE))
     bigPslDotPlot(tdb, bbi, seqName, winStart, winEnd);
 
 boolean showEvery = sameString(item, "PrintAllSequences");
 boolean showAll = trackDbSettingOn(tdb, "showAll");
 unsigned seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
 struct bigBedInterval *bb, *bbList = NULL;
+struct hash *chainHash = NULL;
+struct hash *mapPsls = NULL;     // mapping alignments quickLift reuses across items
+char *quickLiftFile = trackDbSetting(tdb, "quickLiftUrl");
 
+// A quickLifted track can only show what the chains around this window reach, so it takes
+// the windowed query even when the track asks for every alignment of the item.  The file
+// holds the other assembly's alignments, so the window has to be turned into that
+// assembly's coordinates before the query.  quickLiftGetIntervals also hands back the
+// chains needed to bring the alignments the other way.
 // If showAll is on, show all alignments with this qName, not just the
 // selected one.
-if (showEvery)
+if (quickLiftFile != NULL)
+    bbList = quickLiftGetIntervals(quickLiftFile, bbi, seqName, ivStart, ivEnd, &chainHash);
+else if (showEvery)
     {
     struct bbiChromInfo *chrom, *chromList = bbiChromList(bbi);
     for (chrom = chromList; chrom != NULL; chrom = chrom->next)
         {
         char *chromName = chrom->name;
         int start = 0, end = chrom->size;
         int itemsLeft = 0;  // Zero actually means no limit.... 
         struct bigBedInterval *intervalList = bigBedIntervalQuery(bbi, chromName,
             start, end, itemsLeft, lm);
         slCat(&bbList, intervalList);
         }
     }
 else if (showAll)
     {
     int fieldIx;
@@ -3529,30 +3539,38 @@
 
 boolean firstTime = TRUE;
 struct hash *seqHash = hashNew(0);
 struct dyString *sequencesText = dyStringNew(256);
 int sequencesFound = 0;
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
     bbiCachedChromLookup(bbi, bb->chromId, lastChromId, chromName, sizeof(chromName));
 
     lastChromId=bb->chromId;
     bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, 4);
     if (showEvery || sameString(bedRow[3], item))
 	{
         char *cdsStr, *seq;
         struct psl *psl= getPslAndSeq(tdb, chromName, bb, seqTypeField, &seq, &cdsStr);
+        if (chainHash != NULL)
+            {
+            struct psl *lifted = quickLiftPsl(chainHash, &mapPsls, psl);
+            pslFree(&psl);
+            if (lifted == NULL)
+                continue;       // nothing in the chains places this alignment
+            psl = lifted;
+            }
         slAddHead(&pslList, psl);
 
         // we're assuming that if there are multiple psl's with the same id that
         // they are the same query sequence so we only put out one set of sequences
         if (!hashLookup(seqHash, bedRow[3]) && !isEmpty(seq))    // if there is a query sequence
             {
             if (firstTime)
 		{
 		firstTime = FALSE;
 		printf("<H3>Links to sequence:</H3>\n");
 		printf("<UL>\n");
 		}
 
             if (!isEmpty(cdsStr))  // if we have CDS 
                 {
@@ -3588,62 +3606,77 @@
 if (!firstTime)
     printf("</UL>\n");
 freeHash(&seqHash);
 
 char *sort = cartUsualString(cart, "sort", pslSortList[0]);
 pslSortListByVar(&pslList, sort);
 
 if (showEvery)
     printf("<H3>Genomic Alignments</H3>");
 else
     printf("<H3>%s/Genomic Alignments</H3>", item);
 /* Hub track names have special characters replaced with underbar, but tdb->table does not. */
 char *aliTable = cloneString(tdb->table);
 if (isHubTrack(aliTable))
     trackHubFixName(aliTable);
-if (showEvery || pslIsProtein(pslList))
+// pslIsProtein reads through its argument, and the list is empty whenever nothing in the
+// window matched the item, or, on a quickLifted track, nothing in the window could be
+// lifted.
+if (showEvery || ((pslList != NULL) && pslIsProtein(pslList)))
     printAlignmentsSimple(pslList, start, "htcBigPslAli", aliTable, item);
 else
     printAlignmentsExtra(pslList, start, "htcBigPslAli", "htcBigPslAliInWindow",
         aliTable, item);
 freeMem(aliTable);
 pslFreeList(&pslList);
 
 
 if (showEvery && sequencesFound > 0)
     {  
     printf("<BR>\n");
     printf("Input Sequences:<BR>\n");
     printf("<textarea rows='8' cols='60' readonly>\n");
     printf("%s", sequencesText->string);
     printf("</textarea>\n");
     dyStringFree(&sequencesText);
     }
 
 printItemDetailsHtml(tdb, item);
 }
 
 void genericPslClick(struct sqlConnection *conn, struct trackDb *tdb,
                      char *item, int start, char *subType)
 /* Handle click in generic psl track. */
 {
 struct psl* pslList = getAlignments(conn, tdb->table, item);
 
+// For a quickLifted track the alignments came out of the other assembly, and so did the
+// sequence the check below looks for, so both have to name that assembly.  Move the
+// alignments onto the reference before anything prints a position.  Only the ones the
+// chains around this window can place survive, which leaves out alignments of the same
+// accession elsewhere in the genome.
+char *liftDb = trackDbSetting(tdb, "quickLiftDb");
+char *srcDb = (liftDb != NULL) ? liftDb : database;
+char *quickLiftFile = trackDbSetting(tdb, "quickLiftUrl");
+if ((quickLiftFile != NULL) && (pslList != NULL))
+    pslList = quickLiftPsls(quickLiftChainHash(quickLiftFile, seqName, winStart, winEnd),
+                            pslList);
+
 /* check if there is an alignment available for this sequence.  This checks
  * both genbank sequences and other sequences in the seq table.  If so,
  * set it up so they can click through to the alignment. */
-if (hGenBankHaveSeq(database, item, NULL))
+if (hGenBankHaveSeq(srcDb, item, NULL))
     {
     printf("<H3>%s/Genomic Alignments</H3>", item);
     if (sameString("protein", subType))
         printAlignments(pslList, start, "htcProteinAli", tdb->table, item);
     else
         printAlignments(pslList, start, "htcCdnaAli", tdb->table, item);
     }
 else
     {
     /* just dump the psls */
     pslDumpHtml(pslList);
     }
 pslFreeList(&pslList);
 printItemDetailsHtml(tdb, item);
 }
@@ -6960,30 +6993,42 @@
 if (end != 0 && differentString(chrom,"0") && isNotEmpty(chrom))
     {
     printf("<B>Position:</B> "
            "<A HREF=\"%s&db=%s&position=%s%%3A%d-%d\">",
                   hgTracksPathAndSettings(), database, chrom, start+1, end);
     printf("%s:%d-%d</A><BR>\n", chrom, start+1, end);
     }
 
 gbWarnFree(&gbWarn);
 sqlFreeResult(&sr);
 dyStringFree(&dy);
 hFreeConn(&conn);
 hFreeConn(&conn2);
 }
 
+static char *aliTrackParam()
+/* "&aliTrack=<track>" for the track hgc was called on, so an alignment handler can find
+ * its trackDb.  The aliTable name alone will not do:  a quickLifted track's table name is
+ * the one from the assembly the alignments came from, and that name usually also belongs
+ * to a real table on the assembly being viewed. */
+{
+static char buf[256];
+
+safef(buf, sizeof buf, "&aliTrack=%s", cgiUsualString("table", cgiUsualString("g", "")));
+return buf;
+}
+
 static boolean isPslToPrintByClick(struct psl *psl, int startFirst, boolean isClicked)
 /* Determine if a psl should be printed based on if it was or was not the one that was clicked
  * on.
  */
 {
 return ((psl->tStart == startFirst) && sameString(psl->tName, seqName)) == isClicked;
 }
 
 void printAlignmentsSimple(struct psl *pslList, int startFirst, char *hgcCommand,
                            char *tableName, char *itemIn)
 /* Print list of mRNA alignments, don't add extra textual link when
  * doesn't honor hgcCommand. */
 {
 struct psl *psl;
 int aliCount = slCount(pslList);
@@ -6999,31 +7044,32 @@
 if (startsWith("chr", pslList->tName))
     printf("BROWSER | SIZE IDENTITY CHROMOSOME  STRAND    START     END              QUERY      START  END  TOTAL\n");
 else
     printf("BROWSER | SIZE IDENTITY  SCAFFOLD   STRAND    START     END              QUERY      START  END  TOTAL\n");
 printf("-----------------------------------------------------------------------------------------------------\n");
 for (isClicked = 1; isClicked >= 0; isClicked -= 1)
     {
     for (psl = pslList; psl != NULL; psl = psl->next)
 	{
 	if (isPslToPrintByClick(psl, startFirst, isClicked))
 	    {
             char otherString[512];
             char *qName = itemIn;
 	    if (showEvery)
 		qName = replaceChars(itemIn, "PrintAllSequences", psl->qName);
-	    safef(otherString, sizeof(otherString), "%d&aliTable=%s", psl->tStart, tableName);
+	    safef(otherString, sizeof(otherString), "%d&aliTable=%s%s", psl->tStart, tableName,
+                  aliTrackParam());
             printf("<A HREF=\"%s&db=%s&position=%s%%3A%d-%d\">browser</A> | ",
                    hgTracksPathAndSettings(), database, psl->tName, psl->tStart+1, psl->tEnd);
 	    if (psl->qSize <= MAX_DISPLAY_QUERY_SEQ_SIZE) // Only anchor if small enough 
 		hgcAnchorWindow(hgcCommand, qName, psl->tStart, psl->tEnd, otherString, psl->tName);
             char *displayChromName = chromAliasGetDisplayChrom(database, cart, psl->tName);
 	    printf("%5d  %5.1f%%  %9s     %s %9d %9d  %20s %5d %5d %5d",
 		   psl->match + psl->misMatch + psl->repMatch,
 		   100.0 - pslCalcMilliBad(psl, TRUE) * 0.1,
 		   skipChr(displayChromName), psl->strand, psl->tStart + 1, psl->tEnd,
 		   psl->qName, psl->qStart+1, psl->qEnd, psl->qSize);
 	    if (psl->qSize <= MAX_DISPLAY_QUERY_SEQ_SIZE)
 	        printf("</A>");
 	    printf("\n");
 
 	    if (showEvery)
@@ -7047,32 +7093,32 @@
     {
     if ( pslTrimToTargetRange(psl, winStart, winEnd) != NULL
         &&
 	!startsWith("xeno", tableName)
 	&& !(startsWith("user", tableName) && pslIsProtein(psl))
 	&& psl->tStart == startFirst
         && sameString(psl->tName, seqName)
 	)
 	{
         boolean showEvery = (strstr(itemIn, "PrintAllSequences") > 0);
 	char *qName = itemIn;
 	if (showEvery)
 	    qName = replaceChars(itemIn, "PrintAllSequences", psl->qName);
 
         char otherString[512];
-	safef(otherString, sizeof(otherString), "%d&aliTable=%s",
-	      psl->tStart, tableName);
+	safef(otherString, sizeof(otherString), "%d&aliTable=%s%s",
+	      psl->tStart, tableName, aliTrackParam());
 	hgcAnchorSomewhere(hgcCommandInWindow, qName, otherString, psl->tName);
 	printf("<BR>View details of parts of alignment within browser window</A>.<BR>\n");
 	}
     }
 }
 
 void printAlignments(struct psl *pslList, int startFirst, char *hgcCommand,
 		     char *tableName, char *itemIn)
 /* Print list of mRNA alignments. */
 {
 printAlignmentsExtra(pslList, startFirst, hgcCommand, "htcCdnaAliInWindow", tableName, itemIn);
 }
 
 static struct psl *getAlignmentsTName(struct sqlConnection *conn, char *table, char *acc,
                                       char *tName)
@@ -8481,41 +8527,117 @@
 puts("<FRAMESET COLS = \"13%,87% \" >");
 printf("  <FRAME SRC=\"%s\" NAME=\"index\">\n", indexTn.forCgi);
 // Start the body frame at the #cDNAStart anchor.  This used to be an ONLOAD
 // attribute on the FRAMESET, but our CSP puts a nonce in script-src, so
 // browsers ignore 'unsafe-inline' and never run an inline event handler.
 if (partPsl != wholePsl)
     printf("  <FRAME SRC=\"%s#cDNAStart\" NAME=\"body\">\n", bodyTn.forCgi);
 else
     printf("  <FRAME SRC=\"%s\" NAME=\"body\">\n", bodyTn.forCgi);
 puts("<NOFRAMES><BODY></BODY></NOFRAMES>");
 puts("</FRAMESET>");
 puts("</HTML>\n");
 exit(0);	/* Avoid cartHtmlEnd. */
 }
 
-static void getCdsStartAndStop(struct sqlConnection *conn, char *acc, char *trackTable,
-			       uint *retCdsStart, uint *retCdsEnd)
-/* Get cds start and stop, if available */
+struct quickLiftAli
+/* Where the alignments behind an "aliTable" cart value actually live.  For a quickLifted
+ * track that is another assembly, under the track's unprefixed table name.  For anything
+ * else it is the current database and the table as given. */
+    {
+    struct trackDb *tdb;
+    char *db;              /* assembly holding the alignments and their sequence */
+    char *table;           /* the alignment table within that assembly */
+    char *quickLiftFile;   /* the chain file, NULL when the track is not quickLifted */
+    };
+
+static void quickLiftAliInfo(char *aliTable, struct quickLiftAli *ali)
+/* Fill in where the alignments behind aliTable live. */
+{
+ZeroVar(ali);
+ali->db = database;
+ali->table = aliTable;
+
+char *bareTable = trackHubSkipHubName(aliTable);
+// aliTrack is the track hgc was called on, which is the only unambiguous handle on the
+// trackDb; aliTable can be a table name that both assemblies have.
+char *aliTrack = cartUsualString(cart, "aliTrack", NULL);
+if (isNotEmpty(aliTrack))
+    ali->tdb = hashFindVal(trackHash, aliTrack);
+if ((ali->tdb == NULL) && isCustomTrack(bareTable))
+    {
+    struct customTrack *ct = lookupCt(bareTable);
+    if (ct != NULL)
+        ali->tdb = ct->tdb;
+    }
+if (ali->tdb == NULL)
+    ali->tdb = hashFindVal(trackHash, aliTable);
+if (ali->tdb == NULL)
+    return;
+
+char *liftDb = trackDbSetting(ali->tdb, "quickLiftDb");
+if (liftDb == NULL)
+    return;
+
+ali->quickLiftFile = trackDbSetting(ali->tdb, "quickLiftUrl");
+ali->db = liftDb;
+quickLiftResolveTable(ali->tdb, bareTable, &ali->table, &ali->db);
+}
+
+static struct psl *quickLiftFindPsl(struct quickLiftAli *ali, struct sqlConnection *conn,
+                                    char *acc, char *chrom, int tStart)
+/* The alignment of acc that the lift places at chrom:tStart on the reference.  Only that
+ * destination position is known here and the lift does not run backwards, so read every
+ * alignment of acc out of the other assembly, lift them, and keep the one that lands
+ * where we were sent.  Returns NULL if none does. */
+{
+char splitTable[HDB_MAX_TABLE_STRING];
+boolean hasBin;
+if (!hFindSplitTable(ali->db, chrom, ali->table, splitTable, sizeof splitTable, &hasBin))
+    errAbort("Failed to find aliTable=%s in %s", ali->table, ali->db);
+
+char query[1024];
+sqlSafef(query, sizeof query, "select * from %s where qName like '%s%%'", splitTable, acc);
+struct sqlResult *sr = sqlGetResult(conn, query);
+struct psl *pslList = NULL;
+char **row;
+while ((row = sqlNextRow(sr)) != NULL)
+    slAddHead(&pslList, pslLoad(row+hasBin));
+sqlFreeResult(&sr);
+
+pslList = quickLiftPsls(quickLiftChainHash(ali->quickLiftFile, chrom, winStart, winEnd),
+                        pslList);
+
+struct psl *psl;
+for (psl = pslList; psl != NULL; psl = psl->next)
+    if (sameString(psl->tName, chrom) && (psl->tStart == tStart))
+        return psl;
+return NULL;
+}
+
+static void getCdsStartAndStop(char *db, struct sqlConnection *conn, char *acc,
+                               char *trackTable, uint *retCdsStart, uint *retCdsEnd)
+/* Get cds start and stop, if available.  db is the assembly the alignment came from,
+ * which is not the one on screen when the track is quickLifted. */
 {
 struct trackDb *tdb = hashFindVal(trackHash, trackTable);
 // Note: this variable was previously named cdsTable but unfortunately the
 // hg/(inc|lib)/genbank.[hc] code uses the global var cdsTable!
 char *tdbCdsTable = tdb ? trackDbSetting(tdb, "cdsTable") : NULL;
 if (isEmpty(tdbCdsTable) && startsWith("ncbiRefSeq", trackTable))
     tdbCdsTable = "ncbiRefSeqCds";
-if (isNotEmpty(tdbCdsTable) && hTableExists(database, tdbCdsTable))
+if (isNotEmpty(tdbCdsTable) && hTableExists(db, tdbCdsTable))
     {
     char query[256];
     sqlSafef(query, sizeof(query), "select cds from %s where id = '%s'", tdbCdsTable, acc);
     char *cdsString = sqlQuickString(conn, query);
     if (isNotEmpty(cdsString))
         genbankParseCds(cdsString, retCdsStart, retCdsEnd);
     }
 else if (sqlTableExists(conn, gbCdnaInfoTable))
     {
     char accChopped[512];
     safecpy(accChopped, sizeof(accChopped), acc);
     chopSuffix(accChopped);
     char query[256];
     sqlSafef(query, sizeof query, "select cds from %s where acc = '%s'",
              gbCdnaInfoTable, accChopped);
@@ -8529,287 +8651,351 @@
         }
     }
 }
 
 void htcBigPslAli(char *acc)
 /* Show alignment for accession in bigPsl file. */
 {
 struct psl *psl;
 char *aliTable;
 int start;
 unsigned int cdsStart = 0, cdsEnd = 0;
 struct sqlConnection *conn = NULL;
 struct trackDb *tdb = NULL;
 
 aliTable = cartString(cart, "aliTable");
-if (isCustomTrack(aliTable))
-    {
-    struct customTrack *ct = lookupCt(aliTable);
-    tdb = ct->tdb;
-    }
-else
-    tdb = hashFindVal(trackHash, aliTable);
+// A quickLifted track's alignments live in the file of the assembly they came from, and
+// the position we were sent is on the reference.
+struct quickLiftAli ali;
+quickLiftAliInfo(aliTable, &ali);
+tdb = ali.tdb;
 if (tdb == NULL)
     errAbort("BUG: bigPsl alignment table '%s' not found; this maybe causes by `.' in track names", aliTable);
              
-if (!trackHubDatabase(database))
-    conn = hAllocConnTrack(database, tdb);
+if (!trackHubDatabase(ali.db))
+    conn = hAllocConnTrack(ali.db, tdb);
 
 char title[1024];
 safef(title, sizeof title, "%s vs Genomic [%s]", acc, aliTable);
 alnModernStart(title);
 
 /* Get some environment vars. */
 start = cartInt(cart, "l");
 int end = cartInt(cart, "r");
 char *chrom = cartString(cart, "c");
 
-char *seq, *cdsString = NULL;
+char *seq = NULL, *cdsString = NULL;
 struct lm *lm = lmInit(0);
 char *fileName = bbiNameFromSettingOrTable(tdb, conn, tdb->table);
 struct bbiFile *bbi =  bigBedFileOpenAlias(fileName, chromAliasFindAliases);
-struct bigBedInterval *bb, *bbList = bigBedIntervalQuery(bbi, chrom, start, end, 0, lm);
-char *bedRow[32];
-char startBuf[16], endBuf[16];
+unsigned seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
+struct hash *chainHash = NULL, *mapPsls = NULL;
+struct bigBedInterval *bb, *bbList;
+if (ali.quickLiftFile != NULL)
+    bbList = quickLiftGetIntervals(ali.quickLiftFile, bbi, chrom, start, end, &chainHash);
+else
+    bbList = bigBedIntervalQuery(bbi, chrom, start, end, 0, lm);
+
+// Pick the alignment the browser drew at chrom:start-end.  Under quickLift the intervals
+// are in the other assembly's coordinates, so each one has to be lifted before its
+// position can be compared with the one we were sent.
+psl = NULL;
+char otherChrom[bbi->chromBpt->keySize+1];
+int lastChromId = -1;
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
-    bigBedIntervalToRow(bb, seqName, startBuf, endBuf, bedRow, ArraySize(bedRow));
-    struct bed *bed = bedLoadN(bedRow, 12);
-    if (sameString(bed->name, acc) && (bb->start == start) && (bb->end == end))
+    char *bbChrom = seqName;
+    if (ali.quickLiftFile != NULL)
+        {
+        bbiCachedChromLookup(bbi, bb->chromId, lastChromId, otherChrom, sizeof otherChrom);
+        lastChromId = bb->chromId;
+        bbChrom = otherChrom;
+        }
+    char *bbSeq = NULL, *bbCds = NULL;
+    struct psl *bbPsl = getPslAndSeq(tdb, bbChrom, bb, seqTypeField, &bbSeq, &bbCds);
+    if (ali.quickLiftFile != NULL)
+        {
+        struct psl *lifted = quickLiftPsl(chainHash, &mapPsls, bbPsl);
+        pslFree(&bbPsl);
+        bbPsl = lifted;
+        }
+    if ((bbPsl != NULL) && sameString(bbPsl->qName, acc)
+     && (bbPsl->tStart == start) && (bbPsl->tEnd == end))
         {
-	bb->next = NULL;
+        psl = bbPsl;
+        seq = bbSeq;
+        cdsString = bbCds;
         break;
         }
+    pslFree(&bbPsl);
     }
-if (bb == NULL)
+if (psl == NULL)
     errAbort("item %s not found in range %s:%d-%d in bigBed %s (%s)",
              acc, chrom, start, end, tdb->table, fileName);
-unsigned seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
-psl = getPslAndSeq(tdb, seqName, bb, seqTypeField, &seq, &cdsString);
 if (cdsString)
     genbankParseCds(cdsString,  &cdsStart, &cdsEnd);
 
 
 if (seq == NULL)
     {
     printf("Sequence for %s not available.\n", psl->qName);
     return;
     }
 struct dnaSeq *rnaSeq = newDnaSeq(seq, strlen(seq), acc);
 enum gfType type = gftRna;
 if (pslIsProtein(psl))
     type = gftProt;
 showSomeAlignment(psl, rnaSeq, type, 0, rnaSeq->size, NULL, cdsStart, cdsEnd);
 }
 
 void htcBigPslAliInWindow(char *acc)
 /* Show alignment in window for accession in bigPsl file. */
 {
 struct psl *partPsl, *wholePsl;
 char *aliTable;
 int start;
 unsigned int cdsStart = 0, cdsEnd = 0;
 struct trackDb *tdb = NULL;
 
 aliTable = cartString(cart, "aliTable");
-if (isCustomTrack(aliTable))
-    {
-    struct customTrack *ct = lookupCt(aliTable);
-    tdb = ct->tdb;
-    }
-else
-    tdb = hashFindVal(trackHash, aliTable);
+struct quickLiftAli ali;
+quickLiftAliInfo(aliTable, &ali);
+tdb = ali.tdb;
 char title[1024];
 safef(title, sizeof title, "%s vs Genomic [%s]", acc, aliTable);
 htmlFramesetStart(title);
 
 /* Get some environment vars. */
 start = cartInt(cart, "l");
 int end = cartInt(cart, "r");
 char *chrom = cartString(cart, "c");
 
-char *seq, *cdsString = NULL;
+char *seq = NULL, *cdsString = NULL;
 struct lm *lm = lmInit(0);
 char *fileName = bbiNameFromSettingOrTable(tdb, NULL, tdb->table);
 struct bbiFile *bbi =  bigBedFileOpenAlias(fileName, chromAliasFindAliases);
-struct bigBedInterval *bb, *bbList = bigBedIntervalQuery(bbi, chrom, start, end, 0, lm);
-char *bedRow[32];
-char startBuf[16], endBuf[16];
+unsigned seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
+struct hash *chainHash = NULL, *mapPsls = NULL;
+struct bigBedInterval *bb, *bbList;
+if (ali.quickLiftFile != NULL)
+    // the file holds the other assembly's alignments, so the window has to be turned
+    // into that assembly's coordinates before the query
+    bbList = quickLiftGetIntervals(ali.quickLiftFile, bbi, chrom, start, end, &chainHash);
+else
+    bbList = bigBedIntervalQuery(bbi, chrom, start, end, 0, lm);
+
+wholePsl = NULL;
+char otherChrom[bbi->chromBpt->keySize+1];
+int lastChromId = -1;
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
-    bigBedIntervalToRow(bb, seqName, startBuf, endBuf, bedRow, ArraySize(bedRow));
-    struct bed *bed = bedLoadN(bedRow, 12);
-    if (sameString(bed->name, acc))
+    char *bbChrom = seqName;
+    if (ali.quickLiftFile != NULL)
+        {
+        bbiCachedChromLookup(bbi, bb->chromId, lastChromId, otherChrom, sizeof otherChrom);
+        lastChromId = bb->chromId;
+        bbChrom = otherChrom;
+        }
+    char *bbSeq = NULL, *bbCds = NULL;
+    struct psl *bbPsl = getPslAndSeq(tdb, bbChrom, bb, seqTypeField, &bbSeq, &bbCds);
+    if (ali.quickLiftFile != NULL)
+        {
+        struct psl *lifted = quickLiftPsl(chainHash, &mapPsls, bbPsl);
+        pslFree(&bbPsl);
+        bbPsl = lifted;
+        }
+    if ((bbPsl != NULL) && sameString(bbPsl->qName, acc))
         {
-	bb->next = NULL;
+        wholePsl = bbPsl;
+        seq = bbSeq;
+        cdsString = bbCds;
         break;
         }
+    pslFree(&bbPsl);
     }
-unsigned seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
-wholePsl = getPslAndSeq(tdb, seqName, bb, seqTypeField, &seq, &cdsString);
+if (wholePsl == NULL)
+    errAbort("item %s not found in range %s:%d-%d in bigBed %s (%s)",
+             acc, chrom, start, end, tdb->table, fileName);
 
 if (seq == NULL)
     {
     printf("Sequence for %s not available.\n", wholePsl->qName);
     return;
     }
 if (cdsString)
     genbankParseCds(cdsString,  &cdsStart, &cdsEnd);
 
 if (wholePsl->tStart >= winStart && wholePsl->tEnd <= winEnd)
     partPsl = wholePsl;
 else
     partPsl = pslTrimToTargetRange(wholePsl, winStart, winEnd);
 struct dnaSeq *rnaSeq = newDnaSeq(seq, strlen(seq), acc);
 showSomePartialDnaAlignment(partPsl, wholePsl, rnaSeq,
                             NULL, cdsStart, cdsEnd);
 }
 
-static struct dnaSeq *getBaseColorSequence(char *itemName, char *table)
-/* Grab sequence using the sequence and extFile table names out of BASE_COLOR_USE_SEQUENCE. */
+static struct dnaSeq *getBaseColorSequence(char *db, char *itemName, char *table)
+/* Grab sequence using the sequence and extFile table names out of BASE_COLOR_USE_SEQUENCE.
+ * db is the assembly the sequence lives in, which is not the one on screen when the track
+ * is quickLifted. */
 {
 struct trackDb *tdb = hashMustFindVal(trackHash, table);
 char *spec = trackDbRequiredSetting(tdb, BASE_COLOR_USE_SEQUENCE);
 char *specCopy = cloneString(spec);
 
 // value is: extFile seqTbl extFileTbl
 // or:       db [dddBbb1]
 char *words[3];
 int nwords = chopByWhite(specCopy, words, ArraySize(words));
 if (sameString(words[0], "extFile") && (nwords == ArraySize(words)))
-    return hDnaSeqGet(database, itemName, words[1], words[2]);
+    return hDnaSeqGet(db, itemName, words[1], words[2]);
 else if (sameString(words[0], "db"))
     {
-    char *db = (nwords == 2) ? words[1] : database;
-    return hChromSeq(db, itemName, 0, 0);
+    char *seqDb = (nwords == 2) ? words[1] : db;
+    return hChromSeq(seqDb, itemName, 0, 0);
     }
 else
     errAbort("invalid %s track setting: %s", BASE_COLOR_USE_SEQUENCE, spec);
 return NULL;
 }
 
 void htcCdnaAli(char *acc)
 /* Show alignment for accession. */
 {
 char query[256];
 char table[HDB_MAX_TABLE_STRING];
 char accTmp[64];
 struct sqlConnection *conn;
 struct sqlResult *sr;
 char **row;
 struct psl *psl;
 struct dnaSeq *rnaSeq;
 char *aliTable;
 int start;
 unsigned int cdsStart = 0, cdsEnd = 0;
 boolean hasBin;
 char accChopped[512] ;
 safef(accChopped, sizeof(accChopped), "%s",acc);
 chopSuffix(accChopped);
 
 aliTable = cartString(cart, "aliTable");
-char *accForTitle = startsWith("ncbiRefSeq", aliTable) ? acc : accChopped;
+char *accForTitle = startsWith("ncbiRefSeq", trackHubSkipHubName(aliTable)) ? acc : accChopped;
 char title[1024];
 safef(title, sizeof title, "%s vs Genomic [%s]", accForTitle, aliTable);
 alnModernStart(title);
 
 /* Get some environment vars. */
 start = cartInt(cart, "o");
 
-conn = hAllocConn(database);
-getCdsStartAndStop(conn, acc, aliTable, &cdsStart, &cdsEnd);
+// A quickLifted track's alignments, and the sequence they are to, live in the assembly
+// they came from, not the one on screen.  The position we were sent is on the reference.
+struct quickLiftAli ali;
+quickLiftAliInfo(aliTable, &ali);
+conn = hAllocConn(ali.db);
+getCdsStartAndStop(ali.db, conn, acc, aliTable, &cdsStart, &cdsEnd);
 
+if (ali.quickLiftFile != NULL)
+    {
+    psl = quickLiftFindPsl(&ali, conn, acc, seqName, start);
+    if (psl == NULL)
+        errAbort("Couldn't find a lifted alignment for %s at %s:%d", acc, seqName, start);
+    }
+else
+    {
     /* Look up alignments in database */
     if (!hFindSplitTable(database, seqName, aliTable, table, sizeof table, &hasBin))
         errAbort("Failed to find aliTable=%s", aliTable);
     sqlSafef(query, sizeof query, "select * from %s where qName like '%s%%' and tName=\"%s\" and tStart=%d",
 	    table, acc, seqName, start);
     sr = sqlGetResult(conn, query);
     if ((row = sqlNextRow(sr)) == NULL)
         errAbort("Couldn't find alignment for %s at %d", acc, start);
     psl = pslLoad(row+hasBin);
     sqlFreeResult(&sr);
+    }
 
 /* get bz rna snapshot for blastz alignments */
-if (sameString("mrnaBlastz", aliTable) || sameString("pseudoMrna", aliTable))
+char *bareAliTable = trackHubSkipHubName(aliTable);
+if (sameString("mrnaBlastz", bareAliTable) || sameString("pseudoMrna", bareAliTable))
     {
-    struct sqlConnection *conn = hAllocConn(database);
+    struct sqlConnection *conn = hAllocConn(ali.db);
     unsigned retId = 0;
     safef(accTmp, sizeof accTmp, "bz-%s", acc);
     if (hRnaSeqAndIdx(accTmp, &rnaSeq, &retId, conn) == -1)
-        rnaSeq = hRnaSeq(database, acc);
+        rnaSeq = hRnaSeq(ali.db, acc);
     hFreeConn(&conn);
     }
-else if (sameString("HInvGeneMrna", aliTable))
+else if (sameString("HInvGeneMrna", bareAliTable))
     {
     /* get RNA accession for the gene id in the alignment */
     sqlSafef(query, sizeof query, "select mrnaAcc from HInv where geneId='%s'", acc);
-    rnaSeq = hRnaSeq(database, sqlQuickString(conn, query));
+    rnaSeq = hRnaSeq(ali.db, sqlQuickString(conn, query));
     }
-else if (sameString("ncbiRefSeqPsl", aliTable) || startsWith("altSeqLiftOverPsl", aliTable) ||
-         startsWith("fixSeqLiftOverPsl", aliTable))
+else if (sameString("ncbiRefSeqPsl", bareAliTable) || startsWith("altSeqLiftOverPsl", bareAliTable) ||
+         startsWith("fixSeqLiftOverPsl", bareAliTable))
     {
-    rnaSeq = getBaseColorSequence(acc, aliTable);
+    rnaSeq = getBaseColorSequence(ali.db, acc, aliTable);
     }
 else
     {
-    char *cdnaTable = NULL;
-    struct trackDb *tdb = hashFindVal(trackHash, aliTable);
-    if (tdb != NULL)
-	cdnaTable = trackDbSetting(tdb, "cdnaTable");
-    if (isNotEmpty(cdnaTable) && hTableExists(database, cdnaTable))
-	rnaSeq = hGenBankGetMrna(database, acc, cdnaTable);
+    char *cdnaTable = (ali.tdb != NULL) ? trackDbSetting(ali.tdb, "cdnaTable") : NULL;
+    if (isNotEmpty(cdnaTable) && hTableExists(ali.db, cdnaTable))
+	rnaSeq = hGenBankGetMrna(ali.db, acc, cdnaTable);
     else
-	rnaSeq = hRnaSeq(database, acc);
+	rnaSeq = hRnaSeq(ali.db, acc);
     }
 
 if (NULL == rnaSeq)
     {
 	printf("RNA sequence not found: '%s'", acc);
     }
 else
     {
-    if (startsWith("xeno", aliTable))
+    if (startsWith("xeno", bareAliTable))
         showSomeAlignment(psl, rnaSeq, gftDnaX, 0, rnaSeq->size, NULL, cdsStart, cdsEnd);
     else
         showSomeAlignment(psl, rnaSeq, gftDna, 0, rnaSeq->size, NULL, cdsStart, cdsEnd);
     }
 hFreeConn(&conn);
 }
 
 void htcCdnaAliInWindow(char *acc)
 /* Show part of alignment in browser window for accession. */
 {
 struct psl *wholePsl, *partPsl;
 struct dnaSeq *rnaSeq;
 char *aliTable;
 int start;
 unsigned int cdsStart = 0, cdsEnd = 0;
 char accChopped[512] ;
 safef(accChopped, sizeof(accChopped), "%s",acc);
 chopSuffix(accChopped);
 
 /* Get some environment vars. */
 aliTable = cartString(cart, "aliTable");
 start = cartInt(cart, "o");
 
-char *accForTitle = startsWith("ncbiRefSeq", aliTable) ? acc : accChopped;
+// a quickLifted track carries a hub_NNN_ prefix; the name tests below are all about the
+// kind of alignment, so they want the name without it
+char *bareAliTable = trackHubSkipHubName(aliTable);
+
+char *accForTitle = startsWith("ncbiRefSeq", bareAliTable) ? acc : accChopped;
 char title[1024];
 safef(title, sizeof title, "%s vs Genomic [%s]", accForTitle, aliTable);
 htmlFramesetStart(title);
 
-if (startsWith("user", aliTable))
+if (startsWith("user", bareAliTable))
     {
     char *pslName, *faName, *qName;
     struct lineFile *lf;
     bioSeq *oSeqList = NULL, *oSeq = NULL;
     struct psl *psl;
     int start;
     enum gfType tt, qt;
     boolean isProt;
     char *ss = cartOptionalString(cart, "ss");
 
     if ((ss != NULL) && !ssFilesExist(ss))
 	{
 	ss = NULL;
 	cartRemove(cart, "ss");
 	errAbort("hgBlat temporary files not found");
@@ -8843,82 +9029,91 @@
     if (psl == NULL)
 	errAbort("Couldn't find alignment at %s:%d", seqName, start);
     oSeqList = faReadAllSeq(faName, !isProt);
     for (oSeq = oSeqList; oSeq != NULL; oSeq = oSeq->next)
 	{
 	if (sameString(oSeq->name, qName))
 	    break;
 	}
     if (oSeq == NULL)
 	errAbort("%s is in %s but not in %s. Internal error.", qName, pslName, faName);
     wholePsl = psl;
     rnaSeq = oSeq;
     }
 else
     {
-    /* Look up alignments in database */
-    struct sqlConnection *conn = hAllocConn(database);
-    getCdsStartAndStop(conn, acc, aliTable, &cdsStart, &cdsEnd);
+    /* Look up alignments in database.  A quickLifted track's alignments, and the
+     * sequence they are to, live in the assembly they came from. */
+    struct quickLiftAli ali;
+    quickLiftAliInfo(aliTable, &ali);
+    struct sqlConnection *conn = hAllocConn(ali.db);
+    getCdsStartAndStop(ali.db, conn, acc, aliTable, &cdsStart, &cdsEnd);
 
+    char query[256];
+    if (ali.quickLiftFile != NULL)
+        {
+        wholePsl = quickLiftFindPsl(&ali, conn, acc, seqName, start);
+        if (wholePsl == NULL)
+            errAbort("Couldn't find a lifted alignment for %s at %s:%d", acc, seqName, start);
+        }
+    else
+        {
         char table[64];
         boolean hasBin;
         if (!hFindSplitTable(database, seqName, aliTable, table, sizeof table, &hasBin))
             errAbort("aliTable %s not found", aliTable);
-    char query[256];
         sqlSafef(query, sizeof(query),
              "select * from %s where qName = '%s' and tName=\"%s\" and tStart=%d", 
              table, acc, seqName, start);
         struct sqlResult *sr = sqlGetResult(conn, query);
         char **row;
         if ((row = sqlNextRow(sr)) == NULL)
             errAbort("Couldn't find alignment for %s at %d", acc, start);
         wholePsl = pslLoad(row+hasBin);
         sqlFreeResult(&sr);
+        }
 
-    if (startsWith("ucscRetroAli", aliTable) || startsWith("retroMrnaAli", aliTable) ||
-        sameString("pseudoMrna", aliTable) || startsWith("altSeqLiftOverPsl", aliTable) ||
-        startsWith("fixSeqLiftOverPsl", aliTable) || startsWith("ncbiRefSeqPsl", aliTable))
+    if (startsWith("ucscRetroAli", bareAliTable) || startsWith("retroMrnaAli", bareAliTable) ||
+        sameString("pseudoMrna", bareAliTable) || startsWith("altSeqLiftOverPsl", bareAliTable) ||
+        startsWith("fixSeqLiftOverPsl", bareAliTable) || startsWith("ncbiRefSeqPsl", bareAliTable))
 	{
-        rnaSeq = getBaseColorSequence(acc, aliTable);
+        rnaSeq = getBaseColorSequence(ali.db, acc, aliTable);
 	}
-    else if (sameString("HInvGeneMrna", aliTable))
+    else if (sameString("HInvGeneMrna", bareAliTable))
 	{
 	/* get RNA accession for the gene id in the alignment */
 	sqlSafef(query, sizeof(query), "select mrnaAcc from HInv where geneId='%s'",
 	      acc);
-	rnaSeq = hRnaSeq(database, sqlQuickString(conn, query));
+	rnaSeq = hRnaSeq(ali.db, sqlQuickString(conn, query));
 	}
     else
 	{
-	char *cdnaTable = NULL;
-	struct trackDb *tdb = hashFindVal(trackHash, aliTable);
-	if (tdb != NULL)
-	    cdnaTable = trackDbSetting(tdb, "cdnaTable");
-	if (isNotEmpty(cdnaTable) && hTableExists(database, cdnaTable))
-	    rnaSeq = hGenBankGetMrna(database, acc, cdnaTable);
+	char *cdnaTable = (ali.tdb != NULL) ? trackDbSetting(ali.tdb, "cdnaTable") : NULL;
+	if (isNotEmpty(cdnaTable) && hTableExists(ali.db, cdnaTable))
+	    rnaSeq = hGenBankGetMrna(ali.db, acc, cdnaTable);
 	else
-	    rnaSeq = hRnaSeq(database, acc);
+	    rnaSeq = hRnaSeq(ali.db, acc);
 	}
     hFreeConn(&conn);
     }
 /* Get partial psl for part of alignment in browser window: */
 if (wholePsl->tStart >= winStart && wholePsl->tEnd <= winEnd)
     partPsl = wholePsl;
 else
     partPsl = pslTrimToTargetRange(wholePsl, winStart, winEnd);
 
-if (startsWith("xeno", aliTable))
+if (startsWith("xeno", bareAliTable))
     errAbort("htcCdnaAliInWindow does not support translated alignments.");
 else
     showSomePartialDnaAlignment(partPsl, wholePsl, rnaSeq,
 				NULL, cdsStart, cdsEnd);
 }
 
 void htcChainAli(char *item)
 /* Draw detailed alignment representation of a chain. */
 {
 struct chain *chain;
 struct psl *fatPsl, *psl = NULL;
 char *track = cartString(cart, "o");
 struct trackDb *tdb = getTdbForTrackName(track);
 char *type = cloneString(tdb->type);
 char *typeWords[2];
@@ -10455,31 +10650,31 @@
 /* NOTE: this fix will not handle the case in which frame is shifted
  * internally or at multiple exons, as when frame-shift gaps occur in
  * an alignment of an mRNA to the genome.  Going to have to come back
  * and address that later... (acs) */
 
 dnaTranslateSome(cdsDna->dna+offset, prot, protBufSize);
 dnaSeqFree(&cdsDna);
 return prot;
 }
 
 
 
 void ncbiRefSeqSequence(char *itemName)
 {
 char *table = cartString(cart, "o");
-struct dnaSeq *rnaSeq = getBaseColorSequence(itemName, table );
+struct dnaSeq *rnaSeq = getBaseColorSequence(database, itemName, table );
 cartHtmlStart("RefSeq mRNA Sequence");
 
 printf("<PRE><TT>");
 printf(">%s\n", itemName);
 faWriteNext(stdout, NULL, rnaSeq->dna, rnaSeq->size);
 printf("</TT></PRE>");
 }
 
 
 void htcGeneMrna(char *geneName)
 /* Display cDNA predicted from genome */
 {
 char *table = cartString(cart, "table");
 cartHtmlStart("Predicted mRNA from Genome");
 struct genePred *gp, *gpList = getGenePredForPosition(table, geneName), *next;
@@ -27758,35 +27953,46 @@
     }
 
 struct customTrack *ct = NULL;
 if (isCustomTrack(track) || isMyVariantsTrack(track))
     {
     struct customTrack *ctList = getCtList();
     for (ct = ctList; ct != NULL; ct = ct->next)
         if (sameString(track, ct->tdb->track))
             break;
     }
 
 if ((!isCustomTrack(track) && !isMyVariantsTrack(track) && dbIsFound)
 ||  ((ct!= NULL) && (((ct->dbTrackType != NULL) &&  sameString(ct->dbTrackType, "maf"))|| sameString(ct->tdb->type, "bigMaf"))))
     {
     trackHash = makeTrackHashWithComposites(database, seqName, TRUE);
-    if (sameString("htcBigPslAli", track) || sameString("htcBigPslAliInWindow", track) )
-	{
-	char *aliTable = cartString(cart, "aliTable");
-	if (isHubTrack(aliTable))	
-	    tdb = hubConnectAddHubForTrackAndFindTdb( database, aliTable, NULL, trackHash);
+    // The alignment click-throughs arrive with the track in aliTrack (aliTable can be a
+    // bare table name that means nothing on this assembly), and a hub track's trackDb --
+    // including a quickLifted one -- only reaches trackHash if its hub is attached here.
+    if (sameString("htcBigPslAli", track) || sameString("htcBigPslAliInWindow", track)
+     || sameString("htcCdnaAli", track) || sameString("htcCdnaAliInWindow", track)
+     || sameString("htcProteinAli", track))
+	{
+	char *aliTrack = cartUsualString(cart, "aliTrack", NULL);
+	char *aliTable = cartUsualString(cart, "aliTable", NULL);
+	char *hubTrack = NULL;
+	if (isNotEmpty(aliTrack) && isHubTrack(aliTrack))
+	    hubTrack = aliTrack;
+	else if (isNotEmpty(aliTable) && isHubTrack(aliTable))
+	    hubTrack = aliTable;
+	if (hubTrack != NULL)
+	    tdb = hubConnectAddHubForTrackAndFindTdb( database, hubTrack, NULL, trackHash);
 	}
     else if (isHubTrack(track))
 	{
 	tdb = hubConnectAddHubForTrackAndFindTdb( database, track, NULL, trackHash);
 	}
     if (parentWigMaf)
         {
         int wordCount, i;
         char *words[16];
         char *typeLine;
         char *wigType = needMem(128);
         tdb = hashFindVal(trackHash, parentWigMaf);
         if (!tdb)
             errAbort("can not find trackDb entry for parentWigMaf track %s.",
                     parentWigMaf);
@@ -29119,31 +29325,31 @@
  * mousBlat track in the human browser would hash to the mm2 database. */
 {
 orgDbHash = hashNew(8);
 }
 
 void cartDoMiddle(struct cart *theCart)
 /* Save cart and do main middle handler. */
 {
 initOrgDbHash();
 cart = theCart;
 doMiddle();
 }
 
 // "u"/"s" are the shared BLAT link's session selectors (loadBlatShareSessionIfAny); exclude them so
 // they are not left in the reader's cart and written into any session they later save.
-char *excludeVars[] = {"Submit", "submit", "g", "i", "aliTable", "addp", "pred", "quickLiftCcds",
-                       "u", "s", NULL};
+char *excludeVars[] = {"Submit", "submit", "g", "i", "aliTable", "aliTrack", "addp", "pred",
+                       "quickLiftCcds", "u", "s", NULL};
 
 int main(int argc, char *argv[])
 {
 long enteredMainTime = clock1000();
 /* 0, 0, == use default 10 second for warning, 20 second for immediate exit */
 issueBotWarning = earlyBotCheck(enteredMainTime, "hgc", delayFraction, 0, 0, "html");
 pushCarefulMemHandler(LIMIT_2or6GB);
 cgiSpoof(&argc,argv);
 cartEmptyShell(cartDoMiddle, hUserCookie(), excludeVars, NULL);
 cgiExitTime("hgc", enteredMainTime);
 return 0;
 }