ef4a5ad1ea45eda4fde693f56f2106c553e87d93
chmalee
  Wed May 20 15:24:30 2026 -0700
Add cnv type to myVariants, refs #33808

diff --git src/hg/hgc/myVariantsClick.c src/hg/hgc/myVariantsClick.c
index 4086546d22e..6c3d2cb4789 100644
--- src/hg/hgc/myVariantsClick.c
+++ src/hg/hgc/myVariantsClick.c
@@ -107,30 +107,32 @@
     /* 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> ");
@@ -152,30 +154,44 @@
         /* 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)
                     {
@@ -203,61 +219,87 @@
                 "  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 */
+        /* 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);
@@ -361,34 +403,37 @@
             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>Alt:</B> %s<BR>\n", 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)