11049a7e4e1de1b3218ec8184cdf5e9df8658681
braney
  Mon Jul 20 10:05:02 2026 -0700
quickLift: highlight the clicked difference on the hgc details page

hgTracks/quickLift.c now encodes the specific difference in each mapbox
item name as chainId.type.chromStart.chromEnd (the "identical" region
boxes keep the bare chain id).

hgc.c parses that item name back into a chain id plus the clicked
region, bolds the matching row in the Deletions/Insertions/Doubles/
Mismatches tables, and prints a note telling the user which row they
clicked. Also adds bigQuickLiftChain to the set of types that suppress
the details-page header item.

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index a99485e8b78..bdc8a98ff62 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -4801,30 +4801,50 @@
 printf("<TR><TD><B>total</B></TD><TD>%f</TD></TR>\n", as->total);
 printf("<TR><TD><B>standard deviation</B></TD><TD>%f</TD></TR>\n", as->stdDev);
 printf("</TABLE>\n");
 }
 
 void quickLiftChainClick(struct sqlConnection *conn, struct trackDb *tdb,
                        char *item, int start, char *otherDb)
 /* Handle a click on the quickChain track */
 {
 struct twoBitFile *otherTbf = getOtherTwoBitUrl(tdb);
 char *otherOrg = NULL;
 struct chain *chain = NULL, *subChain = NULL, *toFree = NULL;
 int chainWinSize;
 boolean otherIsActive = FALSE;
 
+// The item name is either a bare chain id (from the "identical" regions) or
+// "chainId.type.chromStart.chromEnd" identifying the specific difference that
+// was clicked.  Pull the chain id off the front and remember the clicked
+// region (if any) so we can bold it in the tables below.
+char *chainItem = cloneString(item);
+long clickStart = -1, clickEnd = -1;
+char *dot = strchr(chainItem, '.');
+if (dot != NULL)
+    {
+    *dot = 0;
+    char *rest = dot + 1;
+    char *words[4];
+    if (chopByChar(rest, '.', words, ArraySize(words)) == 3)
+        {
+        clickStart = atol(words[1]);
+        clickEnd = atol(words[2]);
+        }
+    }
+item = chainItem;
+
 if (hDbIsActive(otherDb))
     otherIsActive = TRUE;
 
 if (! sameWord(otherDb, "seq"))
     {
     otherOrg = hOrganism(otherDb);
     }
 if (otherOrg == NULL)
     {
     /* use first word of chain label (count on org name as first word) */
     otherOrg = firstWordInLine(cloneString(tdb->shortLabel));
     }
 
 chain = chainLoadItemInRange(tdb, item);
 if (startsWith("big", tdb->type))
@@ -4893,115 +4913,124 @@
         case QUICKTYPE_INSERT:
             insertions++;
             break;
         case QUICKTYPE_DEL:
             deletions++;
             break;
         case QUICKTYPE_DOUBLE:
             doubles++;
             break;
         case QUICKTYPE_MISMATCH:
             mismatches++;
             break;
         }
     }
 
+// If the click came from a specific difference (not one of the "identical"
+// regions), the matching row is shown in bold in the tables below.
+if (clickStart >= 0)
+    printf("<BR>The item you clicked on is shown in <B>bold</B> in the tables below.<BR>\n");
+
 if (deletions)
     {
     printf("<BR><B>Deletions in Window:</B><BR>");
     printf("<TABLE border=\"1\"> <TR>\n");
     printf("<TR><TD>%s Position</TD><TD>%s Position</TD><TD>Bases</TD><TD>Alignment</TD><TR>", trackHubSkipHubName(database), trackHubSkipHubName(otherDb));
     for(hr = regions; hr; hr = hr->next)
         {
         if (hr->type != QUICKTYPE_DEL)
             continue;
 
         char *ourPos, *otherPos;
         snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd);
         ourPos = cloneString(addCommasToPos(database, position));
         snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd);
         otherPos = cloneString(addCommasToPos(database, position));
-        printf("<TR><TD>%s</TD><TD>%s</TD><TD>%.*s</TD><TD>",   ourPos, otherPos, hr->otherBaseCount, hr->otherBases);
+        char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : "";
+        printf("<TR%s><TD>%s</TD><TD>%s</TD><TD>%.*s</TD><TD>",   hilite, ourPos, otherPos, hr->otherBaseCount, hr->otherBases);
         hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track);
             printf("alignment</A></TD></TR>");
 
         }
     printf("</TABLE>");
     }
 
 if (insertions)
     {
     printf("<BR><B>Insertions in Window:</B><BR>");
     printf("<TABLE border=\"1\"> <TR>\n");
     printf("<TR><TD>%s Position</TD><TD>%s Position</TD><TD>Bases</TD><TD>Alignment</TD><TR>", trackHubSkipHubName(database), trackHubSkipHubName(otherDb));
     for(hr = regions; hr; hr = hr->next)
         {
         if (hr->type != QUICKTYPE_INSERT)
             continue;
 
         char *ourPos, *otherPos;
         snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd);
         ourPos = cloneString(addCommasToPos(database, position));
         snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd);
         otherPos = cloneString(addCommasToPos(database, position));
-        printf("<TR><TD>%s</TD><TD>%s</TD><TD>%.*s</TD><TD>",   ourPos, otherPos, hr->baseCount, hr->bases);
+        char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : "";
+        printf("<TR%s><TD>%s</TD><TD>%s</TD><TD>%.*s</TD><TD>",   hilite, ourPos, otherPos, hr->baseCount, hr->bases);
         hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track);
             printf("alignment</A></TD></TR>");
 
         }
     printf("</TABLE>");
     }
 
 if (doubles)
     {
     printf("<BR><B>Double Gaps in Window:</B><BR>");
     printf("<TABLE border=\"1\"> <TR>\n");
     printf("<TR><TD>%s Position</TD><TD>%s Position</TD><TD># Bases in %s</TD><TD>#Bases in %s</TD><TD>Alignment</TD><TR>", trackHubSkipHubName(database), trackHubSkipHubName(otherDb), trackHubSkipHubName(database), trackHubSkipHubName(otherDb));
     for(hr = regions; hr; hr = hr->next)
         {
         if (hr->type != QUICKTYPE_DOUBLE)
             continue;
 
         char *ourPos, *otherPos;
         snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd);
         ourPos = cloneString(addCommasToPos(database, position));
         snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd);
         otherPos = cloneString(addCommasToPos(database, position));
-        printf("<TR><TD>%s</TD><TD>%s</TD><TD>%d</TD><TD>%d</TD><TD>",   ourPos, otherPos, hr->baseCount, hr->otherBaseCount);
+        char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : "";
+        printf("<TR%s><TD>%s</TD><TD>%s</TD><TD>%d</TD><TD>%d</TD><TD>",   hilite, ourPos, otherPos, hr->baseCount, hr->otherBaseCount);
         hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track);
             printf("alignment</A></TD></TR>");
 
         }
     printf("</TABLE>");
     }
 
 if (mismatches)
     {
     printf("<BR><B>Mismatches in Window:</B><BR>");
     printf("<TABLE border=\"1\"> <TR>\n");
     printf("<TR><TD>%s Position</TD><TD>%s Position</TD><TD>Change</TD><TD>Alignment</TD><TR>", trackHubSkipHubName(database), trackHubSkipHubName(otherDb));
     for(hr = regions; hr; hr = hr->next)
         {
         if (hr->type != QUICKTYPE_MISMATCH)
             continue;
 
         char *ourPos, *otherPos;
         snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd);
         ourPos = cloneString(addCommasToPos(database, position));
         snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd);
         otherPos = cloneString(addCommasToPos(database, position));
-        printf("<TR><TD>%s</TD><TD>%s</TD><TD>%.*s -> %.*s</TD><TD>",   ourPos, otherPos, hr->otherBaseCount, hr->otherBases, hr->baseCount, hr->bases);
+        char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : "";
+        printf("<TR%s><TD>%s</TD><TD>%s</TD><TD>%.*s -> %.*s</TD><TD>",   hilite, ourPos, otherPos, hr->otherBaseCount, hr->otherBases, hr->baseCount, hr->bases);
         hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track);
             printf("alignment</A></TD></TR>");
 
         }
     printf("</TABLE>");
     }
 }
 void genericClickHandlerPlus(
         struct trackDb *tdb, char *item, char *itemForUrl, char *plus)
 /* Put up generic track info, with additional text appended after item. */
 {
 char *dupe, *type, *words[16], *headerItem;
 int wordCount;
 int start = cartInt(cart, "o");
 int end = cartInt(cart, "t");
@@ -5023,30 +5052,31 @@
     }
 else if (!trackHubDatabase(database))
     conn = hAllocConnTrack(database, tdb);
 if (itemForUrl == NULL)
     itemForUrl = item;
 dupe = cloneString(tdb->type);
 wordCount = chopLine(dupe, words);
 headerItem = cloneString(item);
 type = words[0];
 
 /* Suppress printing item name in page header, as it is not informative for these types of
  * tracks... */
 if (container == NULL && wordCount > 0)
     {
     if (sameString(type, "maf") || sameString(type, "wigMaf") || sameString(type, "bigMaf") || sameString(type, "netAlign")
+        || sameString(type, "bigQuickLiftChain")
         || sameString(type, "encodePeak"))
         headerItem = NULL;
     else if ((  sameString(type, "narrowPeak")
              || sameString(type, "broadPeak")
              || sameString(type, "gappedPeak") )
          &&  headerItem
          &&  sameString(headerItem, ".") )
         headerItem = NULL;
     }
 /* Print header. */
 genericHeader(tdb, headerItem);
 
 if (differentString(type, "bigInteract") && differentString(type, "interact"))
     {
     // skip generic URL code as these may have multiple items returned for a click