7f65d3da8f2138d847f15a8398f8dcee1a079027
braney
  Fri Aug 7 12:07:59 2026 -0700
quickLift: clip an oversized item to the chains we loaded instead of dropping it, refs #38042

quickLift loads chains for the window plus padding, capped at 1 Mb.  An item that
reaches further than that has ends where no chain reaches, remapRangeList can
place neither of them, and the whole item is dropped even though the part on
screen maps fine.  ClinVar copy number variants run to 159 Mb, so 44 of the 46 in
one window disappeared.

Pull the ends in to what the chains cover before mapping.  The browser never
draws the ends of an item that spans the window, so their exact position does not
matter, and the existing spanned-item merge still reports it.  A clipped end is
snapped to a base inside an aligned block, since remapRangeList will only place a
coordinate that lands on real alignment.

The clip is in the quickLift code, not the shared liftOver remap path.  Only
hgTracks asks for it, so the details page keeps the item's true extent.  Gated by
quickLiftClipToChains, on by default.

diff --git src/hg/lib/liftOver.c src/hg/lib/liftOver.c
index 08b04e4de36..5132f50b076 100644
--- src/hg/lib/liftOver.c
+++ src/hg/lib/liftOver.c
@@ -91,30 +91,56 @@
 
 while ((chain = chainRead(lf)) != NULL)
     liftOverAddChainHash(chainHash, chain);
 }
 
 static struct binElement *findRange(struct hash *chainHash, 
                                 char *chrom, int start, int end)
 /* Find elements that intersect range. */
 {
 struct chromMap *map = hashFindVal(chainHash, chrom);
 if (map == NULL)
     return NULL;
 return binKeeperFind(map->bk, start, (end == start) ? end + 1 : end);
 }
 
+struct chain *liftOverChainForRange(struct hash *chainHash, char *chrom, int start, int end)
+/* Return the chain in chainHash covering the most aligned bases in the given range,
+ * or NULL if none overlap it.  This is the chain remapBlockedBed would also pick. */
+{
+struct binElement *binList = findRange(chainHash, chrom, start, end), *el;
+struct chain *best = NULL;
+int bestOverlap = 0;
+
+for (el = binList; el != NULL; el = el->next)
+    {
+    struct chain *chain = el->val;
+    struct cBlock *b;
+    int overlap = 0;
+
+    for (b = chain->blockList; b != NULL; b = b->next)
+        overlap += positiveRangeIntersection(b->tStart, b->tEnd, start, end);
+    if (overlap > bestOverlap)
+        {
+        bestOverlap = overlap;
+        best = chain;
+        }
+    }
+slFreeList(&binList);
+return best;
+}
+
 static int chainAliSize(struct chain *chain)
 /* Return size of all blocks in chain. */
 {
 struct cBlock *b;
 int total = 0;
 for (b = chain->blockList; b != NULL; b = b->next)
     total += b->qEnd - b->qStart;
 return total;
 }
 
 static int aliIntersectSize(struct chain *chain, int tStart, int tEnd)
 /* How many bases in chain intersect region from tStart to tEnd */
 {
 int total = 0, one;
 struct cBlock *b;