40b4fc89ec72c22cae51bf9a8b9a7e8dd887eebf
chmalee
  Thu Jun 4 15:34:16 2026 -0700
Show MANE-relative HGVS terms on myVariants SNV detail pages, refs #33808

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

diff --git src/hg/hgc/myVariantsClick.c src/hg/hgc/myVariantsClick.c
index 6c3d2cb4789..2cfed4e85f9 100644
--- src/hg/hgc/myVariantsClick.c
+++ src/hg/hgc/myVariantsClick.c
@@ -1,494 +1,602 @@
 /* Handle details pages for myVariants tracks */
 
 /* Copyright (C) 2013 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "hash.h"
 #include "linefile.h"
 #include "hgc.h"
 #include "myVariants.h"
 #include "myVariantsShare.h"
 #include "obscure.h"
 #include "cheapcgi.h"
 #include "hgMaf.h"
 #include "hui.h"
 #include "hCommon.h"
 #include "wikiLink.h"
 #include "jsHelper.h"
 #include "web.h"
 #include "hgConfig.h"
 #include "jsonWrite.h"
 #include "htmshell.h"
+#include "hgHgvs.h"
+#include "variantProjector.h"
+#include "genePred.h"
+#include "genbank.h"
+#include "bigBed.h"
+#include "chromAlias.h"
+
+static struct genePred *getGeneTrackOverlaps(struct trackDb *geneTdb, char *db, char *chrom,
+                                             int start, int end)
+/* Return transcripts in geneTdb overlapping chrom:start-end, from a bigGenePred file
+ * or a genePred SQL table. */
+{
+struct genePred *gpList = NULL;
+if (sameString(geneTdb->type, "bigGenePred"))
+    {
+    char *fileName = hReplaceGbdb(trackDbSetting(geneTdb, "bigDataUrl"));
+    if (isEmpty(fileName))
+        return NULL;
+    struct bbiFile *bbi = bigBedFileOpenAlias(fileName, chromAliasFindAliases);
+    struct lm *lm = lmInit(0);
+    struct bigBedInterval *bb, *bbList = bigBedIntervalQuery(bbi, chrom, start, end, 0, lm);
+    for (bb = bbList;  bb != NULL;  bb = bb->next)
+        {
+        struct genePred *gp = (struct genePred *)genePredFromBigGenePred(chrom, bb);
+        if (gp != NULL)
+            slAddHead(&gpList, gp);
+        }
+    lmCleanup(&lm);
+    bigBedFileClose(&bbi);
+    freeMem(fileName);
+    }
+else
+    {
+    struct sqlConnection *conn = hAllocConn(db);
+    int rowOffset = 0;
+    struct sqlResult *sr = hRangeQuery(conn, geneTdb->table, chrom, start, end, NULL, &rowOffset);
+    char **row;
+    while ((row = sqlNextRow(sr)) != NULL)
+        slAddHead(&gpList, genePredLoad(row + rowOffset));
+    sqlFreeResult(&sr);
+    hFreeConn(&conn);
+    }
+slReverse(&gpList);
+return gpList;
+}
+
+static void printMyVariantsHgvs(struct myVariants *item)
+/* For SNV items, show the genomic HGVS g. term and a c./n. term for each overlapping
+ * transcript in the assembly's default (MANE-first) gene track. */
+{
+if (!sameOk(item->itemType, "snv"))
+    return;
+char *db = item->db;
+struct seqWindow *gSeqWin = chromSeqWindowNew(db, NULL, 0, 0);
+struct bed3 variantBed;
+ZeroVar(&variantBed);
+variantBed.chrom = item->chrom;
+variantBed.chromStart = item->chromStart;
+variantBed.chromEnd = item->chromEnd;
+char *chromAcc = hRefSeqAccForChrom(db, item->chrom);
+/* vpGenomicToTranscript requires an IUPAC alt (or "*"/"<DEL>") and aborts otherwise, and
+ * rewrites "<DEL>" in place; work on a copy, normalize "<DEL>", and skip transcript terms
+ * for any alt it can't project. */
+char *alt = cloneString(item->alt);
+boolean altProjectable = (isAllNt(alt, strlen(alt)) ||
+                          sameString(alt, "*") || sameString(alt, "<DEL>"));
+if (sameString(alt, "<DEL>"))
+    alt[0] = '\0';
+char *hgvsG = hgvsGFromVariant(gSeqWin, &variantBed, alt, chromAcc, FALSE);
+
+struct trackDb *geneTdb = altProjectable ? hgvsDefaultGeneTrack(db) : NULL;
+if (geneTdb != NULL)
+    htmlPrintf("<B>HGVS</B> (relative to %s):<BR>\n", geneTdb->shortLabel);
+else
+    printf("<B>HGVS:</B><BR>\n");
+if (isNotEmpty(hgvsG))
+    htmlPrintf("&nbsp;&nbsp;%s<BR>\n", hgvsG);
+
+if (geneTdb == NULL)
+    return;
+
+struct genePred *gp, *gpList = getGeneTrackOverlaps(geneTdb, db, item->chrom,
+                                                    item->chromStart, item->chromEnd);
+for (gp = gpList;  gp != NULL;  gp = gp->next)
+    {
+    char *seq = txSeqFromGp(db, gp);
+    struct dnaSeq *txSeq = newDnaSeq(seq, strlen(seq), gp->name);
+    struct genbankCds cds;
+    genePredToCds(gp, &cds);
+    struct psl *psl = genePredToPsl(gp, hChromSize(db, gp->chrom), txSeq->size);
+    vpExpandIndelGaps(psl, gSeqWin, txSeq);
+    struct vpTx *vpTx = vpGenomicToTranscript(gSeqWin, &variantBed, alt, psl, txSeq);
+    char *hgvsTx = NULL;
+    if (cds.end > cds.start)
+        hgvsTx = hgvsCFromVpTx(vpTx, gSeqWin, psl, &cds, txSeq, FALSE);
+    else
+        hgvsTx = hgvsNFromVpTx(vpTx, gSeqWin, psl, txSeq, FALSE);
+    if (isNotEmpty(hgvsTx))
+        {
+        if (isNotEmpty(gp->name2))
+            htmlPrintf("&nbsp;&nbsp;%s (%s):%s<BR>\n", gp->name, gp->name2, hgvsTx);
+        else
+            htmlPrintf("&nbsp;&nbsp;%s:%s<BR>\n", gp->name, hgvsTx);
+        }
+    }
+}
 
 void doMyVariantsDetails(struct customTrack *ct, char *itemIdString)
 /* Show details of a myVariants item. */
 {
 jsIncludeFile("hgc.js",NULL);
 char *idString = cloneString(itemIdString);
 char *trackName = ct->tdb->track;
 
 /* Detect shared track and resolve table/permissions via hgcentral so that
  * revoked or downgraded shares no longer return owner data. */
 boolean isShared = isMyVariantsSharedTrack(trackName);
 char *dataOwner = NULL;     /* user whose table holds the data */
 char *scopeProject = NULL;  /* live share's project, or NULL for own track */
 char *scopeDb = NULL;       /* live share's db, or NULL for own track */
 int permission = MYVAR_PERM_READONLY;
 if (isShared)
     {
     struct myVariantsShare *share = myVariantsResolveSharedTrack(trackName, cart);
     if (share == NULL)
         {
         printf("Share is no longer available.\n");
         return;
         }
     /* Shared tracks are per-assembly; reject details requests from other dbs. */
     if (!sameString(share->db, database))
         {
         printf("This share is for a different assembly.\n");
         myVariantsShareFree(&share);
         return;
         }
     dataOwner = cloneString(share->ownerUser);
     scopeProject = cloneString(share->project);
     scopeDb = cloneString(share->db);
     permission = share->permission;
     myVariantsShareFree(&share);
     }
 else
     dataOwner = cloneString(getUserName());
 
 /* Anon users never edit shared items, even when the share is read-write. */
 boolean canEdit = !isShared || (permission == MYVAR_PERM_READWRITE && getUserName() != NULL);
 
 char *tableName = myVariantsGetDbTable(dataOwner);
 /* idString is "<id> <name>": parse id, query by id, verify name matches. */
 char *idStrCopy = cloneString(idString);
 char *expectedName = strchr(idStrCopy, ' ');
 if (expectedName == NULL)
     {
     printf("Invalid item identifier.\n");
     freeMem(idStrCopy);
     return;
     }
 *expectedName++ = '\0';
 if (!isAllDigits(idStrCopy))
     {
     printf("Invalid item identifier.\n");
     freeMem(idStrCopy);
     return;
     }
 unsigned itemId = sqlUnsigned(idStrCopy);
 
 struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH);
 struct dyString *query = sqlDyStringCreate(
     "select * from %s where id=%u", tableName, itemId);
 if (isNotEmpty(scopeDb))
     sqlDyStringPrintf(query, " and db='%s'", scopeDb);
 if (isNotEmpty(scopeProject) && !sameString(scopeProject, "*"))
     sqlDyStringPrintf(query, " and project='%s'", scopeProject);
 struct sqlResult *sr = sqlGetResult(conn, query->string);
 dyStringFree(&query);
 
 struct myVariants *item = NULL;
 char **row = sqlNextRow(sr);
 if (row != NULL)
     {
     item = myVariantsLoad(row);
     if (!sameOk(item->name, expectedName))
         myVariantsFree(&item);
     }
 sqlFreeResult(&sr);
 freeMem(idStrCopy);
 
 if (item != NULL)
     {
     /* Show shared banner */
     if (isShared)
         {
         if (canEdit)
             printf("<div style='padding:6px; margin-bottom:8px; background:#e8f5e9; "
                 "border:1px solid #a5d6a7; border-radius:4px'>"
                 "<B>Shared from %s</B></div>\n", htmlEncode(dataOwner));
         else
             printf("<div style='padding:6px; margin-bottom:8px; background:#e3f2fd; "
                 "border:1px solid #90caf9; border-radius:4px'>"
                 "<B>Shared from %s (read-only)</B></div>\n", htmlEncode(dataOwner));
         }
 
     if (canEdit)
         {
         boolean isTranscript = sameOk(item->itemType, "transcript");
         boolean isCnv = sameOk(item->itemType, "cnv");
         webIncludeResourceFile("spectrum.min.css");
         jsIncludeFile("spectrum.min.js", NULL);
         printf("<FORM ACTION=\"%s\" METHOD=\"POST\">\n\n", hgTracksName());
         cartSaveSession(cart);
 
         /* Save away ID string in hidden var. */
         char varName[128];
         char idStr[128];
         safef(varName, sizeof(varName), "%s_%s", trackName, "id");
         safef(idStr, sizeof(idStr), "%d", item->id);
         cgiMakeHiddenVar(varName, idStr);
 
         /* Put up editable label. */
         safef(varName, sizeof(varName), "%s_%s", trackName, "name");
         printf("<B>Label:</B> ");
         cgiMakeTextVar(varName, item->name, 17);
         printInfoIcon("A short label for this annotation, displayed in the browser.");
         printf("<BR>\n");
 
         /* Put up editable description. */
         safef(varName, sizeof(varName), "%s_%s", trackName, "description");
         printf("<B>Description:</B> ");
         printInfoIcon("Longer notes or comments about this annotation. Displayed on this details page.");
         printf("<BR>\n");
         cgiMakeTextArea(varName, item->description, 8, 80);
         printf("<BR>\n");
 
         /* Non-editable chromosome. */
         htmlPrintf("<B>Chromosome:</B> %s<BR>\n", item->chrom);
 
         /* Editable start and end. */
         int chromSize = hChromSize(database, item->chrom);
         char chromSizeString[16];
         safef(chromSizeString, sizeof(chromSizeString), "%d", chromSize);
         printf("<B>Start:</B> ");
         safef(varName, sizeof(varName), "%s_%s", trackName, "chromStart");
         cgiMakeIntVarInRange(varName, item->chromStart, NULL, 80, "0", chromSizeString);
         printInfoIcon("0-based start position on the chromosome.");
         printf("<BR>\n");
         printf("<B>End:</B> ");
         safef(varName, sizeof(varName), "%s_%s", trackName, "chromEnd");
         cgiMakeIntVarInRange(varName, item->chromEnd, NULL, 80, "1", chromSizeString);
         printInfoIcon("0-based half-open end position on the chromosome.");
         printf("<BR>\n");
 
         if (isTranscript)
             {
             /* Editable CDS Start / CDS End (stored as thickStart/thickEnd). */
             printf("<B>CDS Start:</B> ");
             safef(varName, sizeof(varName), "%s_%s", trackName, "thickStart");
             cgiMakeIntVarInRange(varName, item->thickStart, NULL, 80, "0", chromSizeString);
             printInfoIcon("Start of the coding region.");
             printf("<BR>\n");
             printf("<B>CDS End:</B> ");
             safef(varName, sizeof(varName), "%s_%s", trackName, "thickEnd");
             cgiMakeIntVarInRange(varName, item->thickEnd, NULL, 80, "0", chromSizeString);
             printInfoIcon("End of the coding region.");
             printf("<BR>\n");
 
             /* Blocks (BED12). Hidden inputs are kept in sync by the widget;
              * updateBlocksFields in hgTracks reads them on submit. */
             char vBC[128], vBS[128], vBT[128], vCS[128], vCE[128];
             safef(vBC, sizeof(vBC), "%s_blockCount", trackName);
             safef(vBS, sizeof(vBS), "%s_blockSizes", trackName);
             safef(vBT, sizeof(vBT), "%s_chromStarts", trackName);
             safef(vCS, sizeof(vCS), "%s_chromStart", trackName);
             safef(vCE, sizeof(vCE), "%s_chromEnd", trackName);
             struct dyString *sizesCsv = dyStringNew(64);
             struct dyString *startsCsv = dyStringNew(64);
             int bi;
             for (bi = 0; bi < item->blockCount; bi++)
                 {
                 if (bi > 0)
                     {
                     dyStringAppendC(sizesCsv, ',');
                     dyStringAppendC(startsCsv, ',');
                     }
                 dyStringPrintf(sizesCsv, "%d", item->blockSizes[bi]);
                 dyStringPrintf(startsCsv, "%d", item->chromStarts[bi]);
                 }
             char countStr[32];
             safef(countStr, sizeof(countStr), "%u", item->blockCount);
             printf("<B>Blocks:</B> ");
             printInfoIcon("BED12-style blocks. Leave empty to store a single full-span block.");
             printf("<BR>\n");
             cgiMakeHiddenVarWithIdExtra(vBC, vBC, countStr, NULL);
             cgiMakeHiddenVarWithIdExtra(vBS, vBS, dyStringContents(sizesCsv), NULL);
             cgiMakeHiddenVarWithIdExtra(vBT, vBT, dyStringContents(startsCsv), NULL);
             printf("<div id=\"myVariantsBlocksUi\" style=\"margin:4px 0 12px 0;\"></div>\n");
             jsIncludeFile("myVariantsBlocks.js", NULL);
             jsInlineF(
                 "$(function(){\n"
                 "  if (typeof myVariantsBlocks === 'undefined') { return; }\n"
                 "  var sizesEl = document.getElementById('%s');\n"
                 "  var startsEl = document.getElementById('%s');\n"
                 "  var sizes = sizesEl.value ? sizesEl.value.split(',').map(Number) : [];\n"
                 "  var starts = startsEl.value ? startsEl.value.split(',').map(Number) : [];\n"
                 "  myVariantsBlocks.mount('myVariantsBlocksUi', {\n"
                 "    initialSizes: sizes,\n"
                 "    initialStarts: starts,\n"
                 "    getStart: function(){ return parseInt(document.getElementById('%s').value, 10); },\n"
                 "    getEnd:   function(){ return parseInt(document.getElementById('%s').value, 10); },\n"
                 "    hiddenCountInput:  document.getElementById('%s'),\n"
                 "    hiddenSizesInput:  sizesEl,\n"
                 "    hiddenStartsInput: startsEl\n"
                 "  });\n"
                 "});\n",
                 vBS, vBT, vCS, vCE, vBC);
             dyStringFree(&sizesCsv);
             dyStringFree(&startsCsv);
             }
 
         /* Edit the color */
         safef(varName, sizeof(varName), "%s_%s", trackName, "itemRgb");
         char colorHex[8];
         safef(colorHex, sizeof(colorHex), "#%06X", item->itemRgb);
         hPrintf("<label for=\"%s\"><b>Color:</b></label> ", varName);
         hPrintf("<input type=\"text\" name=\"%s\" id=\"%s\" value=\"%s\">\n",
             varName, varName, colorHex);
         jsInlineF(
             "$(function() {"
                 "$(document.getElementById('%s')).spectrum({"
                     "preferredFormat: 'hex',"
                     "showInput: true,"
                     "showPalette: true,"
                     "hideAfterPaletteSelect: true"
                 "});"
             "});\n",
             varName);
         printf("<br>");
 
         /* Edit ref/alt. Transcript has no alleles; CNV stores a sequence in alt. */
         if (isCnv)
             {
             safef(varName, sizeof(varName), "%s_%s", trackName, "cnvType");
             printf("<B>CNV type:</B> ");
             htmlPrintf("<select name='%s|attr|' id='%s|attr|'>", varName, varName);
             int ci;
             for (ci = 0; ci < myVariantsNumCnvTypes; ci++)
                 {
                 boolean sel = sameOk(item->cnvType, myVariantsCnvTypes[ci]);
                 htmlPrintf("<option value='%s|attr|'%s|none|>%s</option>",
                     myVariantsCnvTypes[ci], sel ? " selected" : "",
                     myVariantsCnvTypes[ci]);
                 }
             printf("</select>");
             printInfoIcon("CNV vocabulary follows gnomAD");
             printf("<BR>\n");
             safef(varName, sizeof(varName), "%s_%s", trackName, "alt");
             printf("<B>Sequence:</B> ");
             cgiMakeTextVar(varName, item->alt, 40);
             printInfoIcon("Inserted or duplicated sequence at this position.");
             printf("<BR>\n");
             }
         else if (!isTranscript)
             {
             safef(varName, sizeof(varName), "%s_%s", trackName, "ref");
             printf("<B>Ref:</B> ");
             cgiMakeTextVar(varName, item->ref, 17);
             printInfoIcon("Reference allele sequence at this position.");
             printf("<BR>\n");
             safef(varName, sizeof(varName), "%s_%s", trackName, "alt");
             printf("<B>Alt:</B> ");
             cgiMakeTextVar(varName, item->alt, 17);
             printInfoIcon("Alternate (variant) allele sequence.");
             printf("<BR>\n");
             }
 
         /* Project: locked for shared tracks, editable for own track */
         printf("<B>Project:</B> ");
         if (isShared)
             {
             htmlPrintf("%s<BR>\n", isNotEmpty(item->project) ? item->project : "(none)");
             }
         else
             {
             safef(varName, sizeof(varName), "%s_%s", trackName, "project");
             struct slName *projects = myVariantsGetProjects(dataOwner);
             if (projects)
                 {
                 char selectName[128];
                 safef(selectName, sizeof(selectName), "%s_projectSelect", trackName);
                 htmlPrintf("<select id='%s|attr|'>", selectName);
                 printf("<option value=''>(none)</option>");
                 struct slName *proj;
                 boolean currentFound = FALSE;
                 for (proj = projects; proj != NULL; proj = proj->next)
                     {
                     boolean isCurrent = sameString(proj->name, item->project);
                     if (isCurrent)
                         currentFound = TRUE;
                     htmlPrintf("<option value='%s|attr|'%s|none|>%s</option>",
                         proj->name, isCurrent ? " selected" : "", proj->name);
                     }
                 if (!currentFound && isNotEmpty(item->project))
                     htmlPrintf("<option value='%s|attr|' selected>%s</option>",
                         item->project, item->project);
                 printf("<option value='__new__'>Add new...</option>");
                 printf("</select> ");
                 htmlPrintf("<input type='text' name='%s|attr|' id='%s|attr|' value='%s|attr|'"
                     " style='display:none' placeholder='Enter new project'>",
                     varName, varName, item->project);
                 slFreeList(&projects);
                 }
             else
                 cgiMakeTextVar(varName, item->project, 40);
             printInfoIcon("Group annotations by project.");
             printf("<BR>\n");
             }
 
         /* Mouseover */
         safef(varName, sizeof(varName), "%s_%s", trackName, "mouseover");
         printf("<B>Mouseover:</B> ");
         cgiMakeTextVar(varName, item->mouseover, 60);
         printInfoIcon("Short text shown when hovering over this item.");
         printf("<BR>\n");
 
         /* Custom fields */
             {
             struct slName *customCols = myVariantsGetCustomFields(dataOwner);
             if (customCols)
                 {
                 struct dyString *cfQuery = dyStringNew(256);
                 struct slName *col;
                 sqlDyStringPrintf(cfQuery, "SELECT ");
                 boolean first = TRUE;
                 for (col = customCols; col != NULL; col = col->next)
                     {
                     if (!first)
                         sqlDyStringPrintf(cfQuery, ", ");
                     sqlDyStringPrintIdList(cfQuery, col->name);
                     first = FALSE;
                     }
                 sqlDyStringPrintf(cfQuery, " FROM %s WHERE id=%d", tableName, item->id);
                 struct sqlResult *cfSr = sqlGetResult(conn, dyStringContents(cfQuery));
                 char **cfRow = sqlNextRow(cfSr);
                 if (cfRow)
                     {
                     int i = 0;
                     for (col = customCols; col != NULL; col = col->next, i++)
                         {
                         safef(varName, sizeof(varName), "%s_%s", trackName, col->name);
                         printf("<B>%s:</B> ", col->name);
                         cgiMakeTextVar(varName, cfRow[i] ? cfRow[i] : "", 40);
                         printf("<BR>\n");
                         }
                     }
                 sqlFreeResult(&cfSr);
                 dyStringFree(&cfQuery);
                 slFreeList(&customCols);
                 }
             }
 
         /* Buttons: Update and Cancel always; Delete only for own track */
         cgiMakeButton("submit", "Update");
         printf(" ");
         if (!isShared)
             {
             safef(varName, sizeof(varName), "%s_%s", trackName, "delete");
             cgiMakeButton(varName, "Delete");
             printf(" ");
             }
         safef(varName, sizeof(varName), "%s_%s", trackName, "cancel");
         cgiMakeButton(varName, "Cancel");
         printf("</FORM>\n");
         }
     else
         {
         /* Read-only display for shared items without write permission.
          * htmlPrintf escapes %s by default; that prevents stored XSS via
          * owner-controlled fields. */
         htmlPrintf("<B>Label:</B> %s<BR>\n", item->name);
         htmlPrintf("<B>Description:</B><BR>\n%s<BR>\n", item->description);
         htmlPrintf("<B>Chromosome:</B> %s<BR>\n", item->chrom);
         printf("<B>Start:</B> %d<BR>\n", item->chromStart);
         printf("<B>End:</B> %d<BR>\n", item->chromEnd);
         if (item->blockCount > 1)
             {
             printf("<B>Blocks:</B> ");
             int roBi;
             for (roBi = 0; roBi < item->blockCount; roBi++)
                 {
                 if (roBi > 0)
                     printf(", ");
                 int relStart = item->chromStarts[roBi];
                 int relEnd = relStart + item->blockSizes[roBi];
                 printf("%d-%d (%dbp)", relStart, relEnd, item->blockSizes[roBi]);
                 }
             printf("<BR>\n");
             }
         char colorHex[8];
         safef(colorHex, sizeof(colorHex), "#%06X", item->itemRgb);
         printf("<B>Color:</B> <span style='background:%s; padding:2px 12px'>&nbsp;</span> %s<BR>\n",
             colorHex, colorHex);
         boolean roIsCnv = sameOk(item->itemType, "cnv");
         if (roIsCnv && isNotEmpty(item->cnvType))
             htmlPrintf("<B>CNV type:</B> %s<BR>\n", item->cnvType);
         if (isNotEmpty(item->ref))
             htmlPrintf("<B>Ref:</B> %s<BR>\n", item->ref);
         if (isNotEmpty(item->alt))
             htmlPrintf("<B>%s:</B> %s<BR>\n", roIsCnv ? "Sequence" : "Alt", item->alt);
         if (isNotEmpty(item->project))
             htmlPrintf("<B>Project:</B> %s<BR>\n", item->project);
         if (isNotEmpty(item->mouseover))
             htmlPrintf("<B>Mouseover:</B> %s<BR>\n", item->mouseover);
 
         /* Custom fields read-only */
             {
             struct slName *customCols = myVariantsGetCustomFields(dataOwner);
             if (customCols)
                 {
                 struct dyString *cfQuery = dyStringNew(256);
                 struct slName *col;
                 sqlDyStringPrintf(cfQuery, "SELECT ");
                 boolean first = TRUE;
                 for (col = customCols; col != NULL; col = col->next)
                     {
                     if (!first)
                         sqlDyStringPrintf(cfQuery, ", ");
                     sqlDyStringPrintIdList(cfQuery, col->name);
                     first = FALSE;
                     }
                 sqlDyStringPrintf(cfQuery, " FROM %s WHERE id=%d", tableName, item->id);
                 struct sqlResult *cfSr = sqlGetResult(conn, dyStringContents(cfQuery));
                 char **cfRow = sqlNextRow(cfSr);
                 if (cfRow)
                     {
                     int i = 0;
                     for (col = customCols; col != NULL; col = col->next, i++)
                         {
                         if (cfRow[i] && cfRow[i][0])
                             htmlPrintf("<B>%s:</B> %s<BR>\n", col->name, cfRow[i]);
                         }
                     }
                 sqlFreeResult(&cfSr);
                 dyStringFree(&cfQuery);
                 slFreeList(&customCols);
                 }
             }
         }
 
     printf("<B>id:</B> %d<BR>\n", item->id);
 
+    printMyVariantsHgvs(item);
+
     /* Overlaps section: only emit if hg.conf names overlap tracks for this assembly.
      * Format: myVariantsOverlapTracks.<db> = track1,track2,...  */
     char overlapKey[256];
     safef(overlapKey, sizeof(overlapKey), "myVariantsOverlapTracks.%s", database);
     char *overlapList = cfgOption(overlapKey);
     if (isNotEmpty(overlapList))
         {
         struct jsonWrite *jw = jsonWriteNew();
         jsonWriteListStart(jw, NULL);
         struct slName *trackNames = slNameListFromComma(overlapList);
         struct slName *t;
         for (t = trackNames; t != NULL; t = t->next)
             jsonWriteString(jw, NULL, t->name);
         jsonWriteListEnd(jw);
         jsInline("var doItemOverlaps = true;\n");
         jsInlineF("var overlapTracks = %s;\n", jw->dy->string);
         printf("<div id='itemOverlaps' style=\"display:none\"></div>\n");
         slFreeList(&trackNames);
         jsonWriteFree(&jw);
         }
 
     printPosOnChrom(item->chrom, item->chromStart, item->chromEnd, NULL, TRUE, NULL);
     }
 
 freeMem(dataOwner);
 hFreeConn(&conn);
 }