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

diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index ba1cda170cb..97b72d6c83e 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -634,180 +634,259 @@
                     $(imageV2.imgTbl).imgAreaSelect({hide:true});
                     $(this).hide();
                     $('body').css('cursor', ''); // Occasionally wait cursor got left behind
                 }
         });
         $(positionDialog).dialog('open');
     }
 
 };
 
   /////////////////////////////////////
  //// Creating items             /////
 /////////////////////////////////////
 var myVariants = {
     createBedForm: function(dialogEle) {
-        // Simple fields shown by default (matches hgc edit form style)
-        const simpleFields = [
+        // Shared top fields (visible in all manual modes)
+        const commonTopFields = [
             { label: "Label", id: "name", type: "text", placeholder: "Optional item label",
-              info: "A short label for this annotation, displayed in the browser." },
+              info: "A short label for this annotation, displayed in the browser" },
             { label: "Color", id: "color", type: "text" },
+        ];
+        // SNV-only field (between commonTop and alt/sequence)
+        const snvOnlyFields = [
             { label: "Ref", id: "ref", type: "text", placeholder: "Optional reference allele sequence",
-              info: "Reference allele sequence at this position." },
-            { label: "Alt", id: "alt", type: "text", placeholder: "Optional alternate allele sequence",
-              info: "Alternate (variant) allele sequence." },
-            { label: "Mouseover", id: "mouseover", type: "text", placeholder: "Short text shown on hover",
-              info: "Short text shown when hovering over this item. If empty, the label and alleles are displayed." },
-            { label: "Description", id: "description", type: "textarea", placeholder: "Longer description/notes",
-              info: "Longer notes or comments about this annotation. Displayed on the details page." },
-            { label: "Project", id: "project", type: "text", placeholder: "Optional project name",
-              info: "Group annotations by project. Projects with no annotations are automatically removed from the list." },
+              info: "Reference allele sequence at this position" },
         ];
-
-        // Advanced fields hidden by default
-        const advancedFields = [
+        // Alt/Sequence field: shown for SNV (label="Alt") and CNV (label="Sequence")
+        const altField = { label: "Alt", id: "alt", type: "text",
+            placeholder: "Optional alternate allele sequence",
+            info: "Alternate (variant) allele sequence" };
+        // Position fields: shown always for Transcript; collapsible for SNV/CNV
+        const positionFields = [
             { label: "Chromosome", id: "chrom", type: "text" },
             { label: "Start", id: "start", type: "number",
-              info: "1-based start position on the chromosome." },
+              info: "1-based start position on the chromosome" },
             { label: "End", id: "end", type: "number",
-              info: "1-based end position on the chromosome (inclusive)." },
+              info: "1-based end position on the chromosome (inclusive)" },
             { label: "Score", id: "score", type: "number",
-              info: "Score from 0-1000. Higher scores display darker." },
+              info: "Score from 0-1000. Higher scores display darker" },
             { label: "Strand", id: "strand", type: "text",
-              info: "Strand: + for forward, - for reverse, . for unknown." },
-            { label: "Thick Start", id: "thickStart", type: "number",
-              info: "Start of thickly drawn region (for display purposes)." },
-            { label: "Thick End", id: "thickEnd", type: "number",
-              info: "End of thickly drawn region (for display purposes)." },
+              info: "Strand: + for forward, - for reverse, . for unknown" },
+        ];
+        // Transcript-only CDS fields (displayed as CDS Start/End; stored as thickStart/thickEnd)
+        const cdsFields = [
+            { label: "CDS Start", id: "thickStart", type: "number",
+              info: "Start of the coding region" },
+            { label: "CDS End", id: "thickEnd", type: "number",
+              info: "End of the coding region" },
         ];
+        // Shared bottom fields
+        const commonBottomFields = [
+            { label: "Mouseover", id: "mouseover", type: "text", placeholder: "Short text shown on hover",
+              info: "Short text shown when hovering over this item. If empty, the label and alleles are displayed" },
+            { label: "Description", id: "description", type: "textarea", placeholder: "Longer description/notes",
+              info: "Longer notes or comments about this annotation. Displayed on the details page" },
+            { label: "Project", id: "project", type: "text", placeholder: "Optional project name",
+              info: "Group annotations by project. Projects with no annotations are automatically removed from the list" },
+        ];
+        // CNV vocabulary, mirrored on the server in myVariantsTrack.c:validCnvTypes
+        const cnvTypeOptions = ["deletion", "duplication", "insertion", "inversion",
+                                "translocation", "complex", "breakend"];
 
         const form = document.createElement("form");
         form.className = "myVariants-form";
         form.action = "hgTracks";
         form.method = "post";
         form.id = "myVariants-form";
 
         // First: HGVS/position input box (primary interaction method)
         let quickInpDiv = document.createElement("div");
         quickInpDiv.id = "hgvsInputDiv";
         form.appendChild(quickInpDiv);
 
         let quickInp = document.createElement("input");
         let quickInpLabel = document.createElement("label");
         quickInp.type = "text";
         quickInp.id = "hgvsInput";
         quickInp.name = "myVariantsHgvsInput";
         quickInp.style.width = "300px";
         quickInpLabel.textContent = "Enter HGVS, position, or gene symbol";
         quickInpLabel.style.display = "inline-block";
         quickInpLabel.style.minWidth = "200px";
         quickInpLabel.style.marginRight = "8px";
         quickInpLabel.for = "hgvsInput";
 
-        // Helper to resize dialog based on current mode
         let resizeDialog = function() {
             var dialog = $("#myVariantsDialog");
             var hgvsVisible = document.getElementById("hgvsInputDiv").style.display !== "none";
-            var advancedVisible = document.getElementById("advancedFieldsDiv") &&
-                                  document.getElementById("advancedFieldsDiv").style.display !== "none";
-
             if (hgvsVisible) {
-                // Quick input mode - compact
                 dialog.dialog("option", "width", 580);
                 dialog.dialog("option", "height", "auto");
-            } else if (advancedVisible) {
-                // Manual mode with advanced fields - larger
-                dialog.dialog("option", "width", Math.min(700, window.innerWidth * 0.8));
-                dialog.dialog("option", "height", Math.min(600, window.innerHeight * 0.8));
+                dialog.dialog("option", "position", { my: "center", at: "center", of: window });
             } else {
-                // Manual mode without advanced fields - medium
-                dialog.dialog("option", "width", Math.min(650, window.innerWidth * 0.8));
-                dialog.dialog("option", "height", "auto");
+                dialog.dialog("option", "width", Math.min(1000, window.innerWidth * 0.9));
+                dialog.dialog("option", "height", Math.min(800, window.innerHeight * 0.9));
+                dialog.dialog("option", "position", { my: "top+30", at: "top", of: window });
             }
-            // Re-center the dialog after resize
-            dialog.dialog("option", "position", { my: "center", at: "center", of: window });
         };
 
-        // Helper to format and update the position summary shown in manual mode
         let updatePositionSummary = function() {
             let summaryText = document.getElementById("positionSummaryText");
             if (!summaryText) return;
             let chrom = document.getElementById("chrom");
             let start = document.getElementById("start");
             let end = document.getElementById("end");
             if (chrom && start && end) {
                 let startVal = parseInt(start.value);
                 let endVal = parseInt(end.value);
                 let startFmt = isNaN(startVal) ? start.value : startVal.toLocaleString();
                 let endFmt = isNaN(endVal) ? end.value : endVal.toLocaleString();
-                summaryText.innerHTML = "<b>Position:</b> " + chrom.value +
-                    ":" + startFmt + "-" + endFmt + " <span style='color:#888'>(from current view)</span>";
+                summaryText.textContent = chrom.value + ":" + startFmt + "-" + endFmt;
             }
         };
 
+        // Switch the form into one of four modes and remember the choice so the
+        // next dialog open restarts in the same place.
+        let applyMode = function(mode) {
+            const isHgvs = mode === "hgvs";
+            const isTranscript = mode === "transcript";
+            const isSnv = mode === "snv";
+            const isCnv = mode === "cnv";
+
+            document.getElementById("hgvsInputDiv").style.display = isHgvs ? "" : "none";
+            document.getElementById("manualInputDiv").style.display = isHgvs ? "none" : "";
+            document.getElementById("hgvsManualToggle").textContent =
+                isHgvs ? "Or edit item fields manually" : "Back to quick input mode";
+
+            if (!isHgvs) {
+                document.getElementById("typeTranscript").checked = isTranscript;
+                document.getElementById("typeSnv").checked = isSnv;
+                document.getElementById("typeCnv").checked = isCnv;
+            }
+
+            document.getElementById("refWrapper").style.display = isSnv ? "" : "none";
+            document.getElementById("altWrapper").style.display = (isSnv || isCnv) ? "" : "none";
+            document.getElementById("altLabel").textContent = isCnv ? "Sequence" : "Alt";
+            document.getElementById("cnvTypeWrapper").style.display = isCnv ? "" : "none";
+
+            if (isTranscript) {
+                advancedDiv.style.display = "none";
+                cdsBlocksDiv.style.display = "none";
+                posWraps.forEach(function(w) { w.style.display = ""; });
+                cdsWraps.forEach(function(w) { w.style.display = ""; });
+                blocksSection.style.display = "";
+                const order = [
+                    typeRadioBar, posSummary,
+                    posWraps[0], posWraps[1], posWraps[2],
+                    nameWrap,
+                    posWraps[3], posWraps[4],
+                    cdsWraps[0], cdsWraps[1],
+                    colorWrap,
+                    mouseoverWrap, descriptionWrap, projectWrap,
+                    blocksSection,
+                    advancedDiv, cdsBlocksDiv,
+                    refWrapper, cnvTypeWrapper, altWrapper,
+                    customFieldsSection
+                ];
+                order.forEach(function(el) { manualInpDiv.appendChild(el); });
+            } else {
+                // Pack wrappers back into their collapsible containers.
+                posWraps.forEach(function(w) { advancedDiv.appendChild(w); });
+                cdsWraps.forEach(function(w) { cdsBlocksDiv.appendChild(w); });
+                cdsBlocksDiv.appendChild(blocksSection);
+                advancedDiv.style.display = "none";
+                cdsBlocksDiv.style.display = "none";
+                const order = [
+                    typeRadioBar, posSummary,
+                    advancedDiv,
+                    nameWrap, colorWrap,
+                    refWrapper, cnvTypeWrapper, altWrapper,
+                    cdsBlocksDiv,
+                    mouseoverWrap, descriptionWrap, projectWrap,
+                    customFieldsSection
+                ];
+                order.forEach(function(el) { manualInpDiv.appendChild(el); });
+            }
+            document.getElementById("positionSummaryDiv").style.display = isTranscript ? "none" : "";
+            if (posEditLink) posEditLink.textContent = "[edit]";
+
+            try { localStorage.setItem("myVariantsLastMode", mode); } catch (e) { /* private mode */ }
+            resizeDialog();
+        };
+
+        let currentType = function() {
+            if (document.getElementById("typeTranscript").checked) return "transcript";
+            if (document.getElementById("typeCnv").checked) return "cnv";
+            return "snv";
+        };
+
         let toggleForm = function(event) {
             event.preventDefault();
-            let hgvsInputStyle = document.getElementById("hgvsInputDiv").style.display;
-            let manualInputStyle = document.getElementById("manualInputDiv").style.display;
-            document.getElementById("hgvsInputDiv").style.display = hgvsInputStyle === "none" ? "" : "none";
-            document.getElementById("manualInputDiv").style.display = manualInputStyle === "none" ? "" : "none";
-            document.getElementById("hgvsManualToggle").textContent = hgvsInputStyle === "none" ? "Or edit item fields manually" : "Back to quick input mode";
-            resizeDialog();
+            const inHgvs = document.getElementById("hgvsInputDiv").style.display !== "none";
+            applyMode(inHgvs ? currentType() : "hgvs");
         };
         let toggleContainer = document.createElement("p");
         let toggle = document.createElement("a");
         toggle.href = "#";
         toggle.id = "hgvsManualToggle";
         toggle.addEventListener("click", toggleForm);
         toggle.textContent = "Or edit item fields manually";
         toggleContainer.appendChild(toggle);
 
         quickInpDiv.appendChild(quickInpLabel);
         quickInpDiv.appendChild(quickInp);
         form.appendChild(toggleContainer);
 
         // Manual input div (contains simple + advanced fields)
         let manualInpDiv = document.createElement("div");
         manualInpDiv.id = "manualInputDiv";
         manualInpDiv.style.display = "none";
 
-        // Position summary line at top of manual form
         let posSummary = document.createElement("div");
         posSummary.id = "positionSummaryDiv";
         posSummary.style.cssText = "margin-bottom:12px; padding:6px 10px; background:#f0f4f8; border:1px solid #d0d7de; border-radius:4px; font-size:13px;";
+        let posSummaryLabel = document.createElement("b");
+        posSummaryLabel.textContent = "Position: ";
+        posSummary.appendChild(posSummaryLabel);
         let posSummaryText = document.createElement("span");
         posSummaryText.id = "positionSummaryText";
         posSummary.appendChild(posSummaryText);
+        let posSummarySuffix = document.createElement("span");
+        posSummarySuffix.style.color = "#888";
+        posSummarySuffix.textContent = " (from current view)";
+        posSummary.appendChild(posSummarySuffix);
         let posEditLink = document.createElement("a");
         posEditLink.href = "#";
         posEditLink.textContent = "[edit]";
         posEditLink.style.cssText = "margin-left:8px; font-size:12px;";
         posEditLink.addEventListener("click", function(event) {
             event.preventDefault();
             let advDiv = document.getElementById("advancedFieldsDiv");
-            let advToggle = document.getElementById("advancedFieldsToggle");
-            if (advDiv && advDiv.style.display === "none") {
+            if (!advDiv) return;
+            if (advDiv.style.display === "none") {
                 advDiv.style.display = "";
-                if (advToggle) advToggle.textContent = "Hide Advanced Fields \u25B2";
-                resizeDialog();
-            }
-            // Scroll to and focus the chrom field
+                posEditLink.textContent = "[hide]";
                 let chromField = document.getElementById("chrom");
                 if (chromField) {
                     chromField.scrollIntoView({behavior: "smooth", block: "nearest"});
                     chromField.focus();
                 }
+            } else {
+                advDiv.style.display = "none";
+                posEditLink.textContent = "[edit]";
+            }
         });
         posSummary.appendChild(posEditLink);
         manualInpDiv.appendChild(posSummary);
 
         // Helper function to create form field (uses createInfoIcon from utils.js)
         let createField = function(field, container) {
             const wrapper = document.createElement("div");
             wrapper.style.marginBottom = "8px";
 
             const label = document.createElement("label");
             label.htmlFor = field.id;
             label.textContent = field.label;
             label.style.display = "inline-block";
             label.style.minWidth = "140px";
 
@@ -927,118 +1006,177 @@
                     if (newProjectInput.value === "" && select.value === "__new__") {
                         select.value = "";
                         newProjectInput.style.display = "none";
                     }
                 });
             } else {
                 // No existing projects - just show text input
                 let input = document.createElement("input");
                 input.type = "text";
                 input.id = "project";
                 input.placeholder = "Optional project name";
                 wrapper.appendChild(input);
             }
 
             // Add info icon
-            wrapper.appendChild(createInfoIcon("Group annotations by project. Projects with no annotations are automatically removed from the list."));
+            wrapper.appendChild(createInfoIcon("Group annotations by project. Projects with no annotations are automatically removed from the list"));
             container.appendChild(wrapper);
         };
 
-        // Create simple fields
+        // Type radio bar: Transcript | Variant -> Short | CNV. Inserted at the very
+        // top of manualInpDiv (before the existing position summary).
+        let typeRadioBar = document.createElement("div");
+        typeRadioBar.id = "typeRadioBar";
+        typeRadioBar.style.cssText = "margin-bottom:12px; padding:8px 10px; background:#f7f7f7; border:1px solid #ddd; border-radius:4px;";
+        typeRadioBar.innerHTML =
+            "<div style='font-weight:bold; font-size:12px; color:#555; margin-bottom:4px;'>Annotation type</div>" +
+            "<div>" +
+                "<label style='margin-right:20px;'><input type='radio' name='myVariantsType' id='typeTranscript' value='transcript'> Transcript</label>" +
+                "<label style='margin-right:20px;'><input type='radio' name='myVariantsType' id='typeSnv' value='snv' checked> Short variant (snv)</label>" +
+                "<label><input type='radio' name='myVariantsType' id='typeCnv' value='cnv'> Copy number variant (cnv)</label>" +
+            "</div>";
+        manualInpDiv.insertBefore(typeRadioBar, manualInpDiv.firstChild);
+
+        let onTypeRadioChange = function() {
+            applyMode(currentType());
+        };
+        // The form is not yet in the document, so query the local subtree.
+        typeRadioBar.querySelectorAll("input[type=radio]").forEach(function(r) {
+            r.addEventListener("change", onTypeRadioChange);
+        });
+
         let colorInput = null;
-        simpleFields.forEach(field => {
-            // Handle project field specially
-            if (field.id === "project") {
-                createProjectField(manualInpDiv);
-            } else {
+        let nameWrap, colorWrap;
+        commonTopFields.forEach(function(field) {
             let input = createField(field, manualInpDiv);
-                if (field.id === "color") {
-                    colorInput = input;
-                }
-            }
+            if (field.id === "color") { colorInput = input; colorWrap = input.parentNode; }
+            else if (field.id === "name") { nameWrap = input.parentNode; }
         });
 
-        // Toggle for advanced fields
-        let advancedToggleContainer = document.createElement("p");
-        let advancedToggle = document.createElement("a");
-        advancedToggle.href = "#";
-        advancedToggle.id = "advancedFieldsToggle";
-        advancedToggle.textContent = "Show Advanced Fields ▼";
-        advancedToggleContainer.appendChild(advancedToggle);
-        manualInpDiv.appendChild(advancedToggleContainer);
+        // SNV-only ref input
+        let refWrapper = document.createElement("div");
+        refWrapper.id = "refWrapper";
+        createField(snvOnlyFields[0], refWrapper);
+        manualInpDiv.appendChild(refWrapper);
+
+        // CNV-only type select
+        let cnvTypeWrapper = document.createElement("div");
+        cnvTypeWrapper.id = "cnvTypeWrapper";
+        cnvTypeWrapper.style.marginBottom = "8px";
+        let cnvTypeLabel = document.createElement("label");
+        cnvTypeLabel.htmlFor = "cnvType";
+        cnvTypeLabel.textContent = "CNV type";
+        cnvTypeLabel.style.cssText = "display:inline-block; min-width:140px;";
+        let cnvTypeSelect = document.createElement("select");
+        cnvTypeSelect.id = "cnvType";
+        cnvTypeOptions.forEach(function(t) {
+            let opt = document.createElement("option");
+            opt.value = t;
+            opt.textContent = t;
+            cnvTypeSelect.appendChild(opt);
+        });
+        cnvTypeWrapper.appendChild(cnvTypeLabel);
+        cnvTypeWrapper.appendChild(cnvTypeSelect);
+        cnvTypeWrapper.appendChild(createInfoIcon("CNV vocabulary follows gnomAD"));
+        manualInpDiv.appendChild(cnvTypeWrapper);
+
+        // Alt / Sequence input (shared between SNV and CNV; label switches in applyMode)
+        let altWrapper = document.createElement("div");
+        altWrapper.id = "altWrapper";
+        altWrapper.style.marginBottom = "8px";
+        let altLabel = document.createElement("label");
+        altLabel.htmlFor = "alt";
+        altLabel.id = "altLabel";
+        altLabel.textContent = "Alt";
+        altLabel.style.cssText = "display:inline-block; min-width:140px;";
+        let altInput = document.createElement("input");
+        altInput.type = "text";
+        altInput.id = "alt";
+        altInput.placeholder = altField.placeholder;
+        altWrapper.appendChild(altLabel);
+        altWrapper.appendChild(altInput);
+        altWrapper.appendChild(createInfoIcon(altField.info));
+        manualInpDiv.appendChild(altWrapper);
 
-        // Advanced fields div (hidden by default)
         let advancedDiv = document.createElement("div");
         advancedDiv.id = "advancedFieldsDiv";
         advancedDiv.style.display = "none";
-        advancedDiv.style.marginLeft = "20px";
-        advancedDiv.style.borderLeft = "2px solid #ccc";
-        advancedDiv.style.paddingLeft = "10px";
+        let posWraps = positionFields.map(function(field) {
+            return createField(field, advancedDiv).parentNode;
+        });
+        manualInpDiv.appendChild(advancedDiv);
 
-        advancedFields.forEach(field => {
-            createField(field, advancedDiv);
+        let cdsBlocksDiv = document.createElement("div");
+        cdsBlocksDiv.id = "cdsBlocksDiv";
+        cdsBlocksDiv.style.display = "none";
+        let cdsWraps = cdsFields.map(function(field) {
+            return createField(field, cdsBlocksDiv).parentNode;
         });
 
-        // Blocks (BED12) section. Empty rows means a single full-span block.
         let blocksSection = document.createElement("div");
         blocksSection.id = "blocksSection";
         blocksSection.style.cssText = "margin-top:12px; padding-top:10px; border-top:1px solid #ddd;";
         let blocksLabel = document.createElement("div");
         blocksLabel.style.cssText = "font-weight:bold; margin-bottom:8px; font-size:13px;";
         blocksLabel.textContent = "Blocks (optional)";
         blocksSection.appendChild(blocksLabel);
         let blocksHint = document.createElement("div");
         blocksHint.style.cssText = "font-size:12px; color:#666; margin-bottom:6px;";
         blocksHint.textContent = "Offsets are relative to Start. First offset must be 0; " +
                                  "last block must reach End. Leave empty for a single full-span block.";
         blocksSection.appendChild(blocksHint);
         let blocksContainer = document.createElement("div");
         blocksContainer.id = "blocksContainer";
         blocksSection.appendChild(blocksContainer);
-        // Hidden inputs that the widget keeps in sync. createItem reads them.
-        let hCount = document.createElement("input");
-        hCount.type = "hidden";
-        hCount.id = "blockCount";
-        let hSizes = document.createElement("input");
-        hSizes.type = "hidden";
-        hSizes.id = "blockSizes";
-        let hStarts = document.createElement("input");
-        hStarts.type = "hidden";
-        hStarts.id = "chromStarts";
+        let hCount = document.createElement("input"); hCount.type = "hidden"; hCount.id = "blockCount";
+        let hSizes = document.createElement("input"); hSizes.type = "hidden"; hSizes.id = "blockSizes";
+        let hStarts = document.createElement("input"); hStarts.type = "hidden"; hStarts.id = "chromStarts";
         blocksSection.appendChild(hCount);
         blocksSection.appendChild(hSizes);
         blocksSection.appendChild(hStarts);
-        advancedDiv.appendChild(blocksSection);
+        cdsBlocksDiv.appendChild(blocksSection);
+        manualInpDiv.appendChild(cdsBlocksDiv);
+
+        let mouseoverWrap, descriptionWrap, projectWrap;
+        commonBottomFields.forEach(function(field) {
+            if (field.id === "project") {
+                createProjectField(manualInpDiv);
+                projectWrap = manualInpDiv.lastElementChild;
+            } else {
+                let input = createField(field, manualInpDiv);
+                if (field.id === "mouseover") mouseoverWrap = input.parentNode;
+                else if (field.id === "description") descriptionWrap = input.parentNode;
+            }
+        });
 
-        // Custom fields section inside advanced fields
         let customFieldsSection = document.createElement("div");
         customFieldsSection.id = "customFieldsSection";
         customFieldsSection.style.cssText = "margin-top:12px; padding-top:10px; border-top:1px solid #ddd;";
 
         let customFieldsLabel = document.createElement("div");
         customFieldsLabel.style.cssText = "font-weight:bold; margin-bottom:8px; font-size:13px;";
         customFieldsLabel.textContent = "Custom Fields";
         customFieldsSection.appendChild(customFieldsLabel);
 
         let customFieldsList = document.createElement("div");
         customFieldsList.id = "customFieldsList";
         customFieldsSection.appendChild(customFieldsList);
 
-        // Reserved field names that cannot be used as custom field names
         let reservedNames = ["bin", "chrom", "chromStart", "chromEnd", "name", "score",
-            "strand", "thickStart", "thickEnd", "itemRgb", "description", "db", "ref",
-            "alt", "project", "mouseover", "id"];
+            "strand", "thickStart", "thickEnd", "itemRgb", "blockCount", "blockSizes",
+            "chromStarts", "description", "db", "ref", "alt", "project", "mouseover",
+            "itemType", "cnvType", "id"];
 
         let validateFieldName = function(nameInput) {
             let name = nameInput.value.trim();
             if (!name) return; // empty is ok, will be skipped on submit
             let valid = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
             if (!valid) {
                 alert("Invalid field name: '" + name + "'. Must start with a letter or underscore, " +
                       "and contain only letters, numbers, and underscores.");
                 nameInput.focus();
                 return false;
             }
             if (name.startsWith("_hidden_")) {
                 alert("Field names cannot start with '_hidden_'.");
                 nameInput.focus();
                 return false;
@@ -1102,54 +1240,50 @@
                 let hideBtn = document.createElement("button");
                 hideBtn.type = "button";
                 hideBtn.textContent = "Hide";
                 hideBtn.title = "Hide this custom field (data preserved, can be restored later)";
                 hideBtn.style.cssText = "border:1px solid #ccc; background:#f5f5f5; color:#888; font-size:11px; cursor:pointer; padding:1px 6px; border-radius:3px;";
                 hideBtn.addEventListener("click", function() {
                     if (!confirm("Hide the '" + existingName + "' field? Data will be preserved but the field will no longer appear."))
                         return;
                     // Send ALTER TABLE CHANGE COLUMN request to rename with _hidden_ prefix
                     let hideUrl = "../cgi-bin/hgTracks?hgt_doJsCommand=" +
                         encodeURIComponent("myVariants myVariants " + JSON.stringify({hideField: existingName})) +
                         "&hgt.trackImgOnly=1&hgt.ideogramToo=1&hgsid=" + getHgsid() + "&db=" + getDb();
                     fetch(hideUrl, { method: "POST", credentials: "same-origin" })
                     .then(function() {
                         row.remove();
-                        resizeDialog();
                     })
                     .catch(function(err) {
                         alert("Error hiding field: " + err.message);
                     });
                 });
                 row.appendChild(hideBtn);
             } else {
                 // Remove button for newly added rows
                 let removeBtn = document.createElement("button");
                 removeBtn.type = "button";
                 removeBtn.textContent = "\u00D7";
                 removeBtn.title = "Remove this field";
                 removeBtn.style.cssText = "border:none; background:none; color:#c00; font-size:18px; cursor:pointer; padding:0 4px; line-height:1;";
                 removeBtn.addEventListener("click", function() {
                     row.remove();
-                    resizeDialog();
                 });
                 row.appendChild(removeBtn);
             }
 
             customFieldsList.appendChild(row);
-            if (document.getElementById("myVariantsDialog"))
-                resizeDialog();
             if (!existingName)
                 nameInput.focus();
         };
 
         // Pre-populate existing custom fields from server
         let existingCustomFields = (typeof hgTracks !== 'undefined' && hgTracks.myVariantsCustomFields)
                                     ? hgTracks.myVariantsCustomFields : [];
         existingCustomFields.forEach(function(fieldName) {
             addCustomFieldRow(fieldName);
         });
 
         let addBtn = document.createElement("button");
         addBtn.type = "button";
         addBtn.id = "addCustomFieldBtn";
         addBtn.textContent = "+ Add Custom Field";
@@ -1186,68 +1320,47 @@
                     // Add it back as a pre-populated existing field row
                     addCustomFieldRow(fieldName);
                     // Remove the chip
                     chip.remove();
                     // Hide the section if no more hidden fields
                     if (hiddenSection.querySelectorAll("span[style]").length <= 1)
                         hiddenSection.style.display = "none";
                 });
                 chip.appendChild(restoreBtn);
                 hiddenSection.appendChild(chip);
             });
 
             customFieldsSection.appendChild(hiddenSection);
         }
 
-        advancedDiv.appendChild(customFieldsSection);
-
-        manualInpDiv.appendChild(advancedDiv);
+        manualInpDiv.appendChild(customFieldsSection);
 
-        // Keep the position summary in sync when advanced fields change
+        // Keep the position summary in sync when position fields change
         ["chrom", "start", "end"].forEach(function(id) {
             let el = advancedDiv.querySelector("#" + id);
-            if (el) {
-                el.addEventListener("input", updatePositionSummary);
-            }
+            if (el) el.addEventListener("input", updatePositionSummary);
         });
-        // Initialize the position summary text using direct references
-        // (the form is not yet in the document, so getElementById won't work)
         let chromEl = advancedDiv.querySelector("#chrom");
         let startEl = advancedDiv.querySelector("#start");
         let endEl = advancedDiv.querySelector("#end");
         if (chromEl && startEl && endEl) {
             let startVal = parseInt(startEl.value);
             let endVal = parseInt(endEl.value);
             let startFmt = isNaN(startVal) ? startEl.value : startVal.toLocaleString();
             let endFmt = isNaN(endVal) ? endEl.value : endVal.toLocaleString();
-            posSummaryText.innerHTML = "<b>Position:</b> " + chromEl.value +
-                ":" + startFmt + "-" + endFmt + " <span style='color:#888'>(from current view)</span>";
-        }
-
-        // Toggle handler for advanced fields
-        advancedToggle.addEventListener("click", function(event) {
-            event.preventDefault();
-            let advDiv = document.getElementById("advancedFieldsDiv");
-            if (advDiv.style.display === "none") {
-                advDiv.style.display = "";
-                advancedToggle.textContent = "Hide Advanced Fields ▲";
-            } else {
-                advDiv.style.display = "none";
-                advancedToggle.textContent = "Show Advanced Fields ▼";
+            posSummaryText.textContent = chromEl.value + ":" + startFmt + "-" + endFmt;
         }
-            resizeDialog();
-        });
 
         form.appendChild(manualInpDiv);
 
         // Add hidden field to encode form values as JSON
         const hiddenInput = document.createElement('input');
         hiddenInput.type = 'hidden';
         hiddenInput.name = 'hgt_doJsCommand';
         form.appendChild(hiddenInput);
 
         const trackNameInput = document.createElement('input');
         trackNameInput.type = 'hidden';
         trackNameInput.id = 'trackName';
         trackNameInput.name = 'trackName';
         trackNameInput.value = "myVariants";
         form.appendChild(trackNameInput);
@@ -1275,30 +1388,32 @@
         // Stash the widget handle on the form so createItem can call validate().
         if (typeof myVariantsBlocks !== "undefined") {
             form.blocksWidget = myVariantsBlocks.mount(blocksContainer, {
                 getStart: function () {
                     return parseInt(document.getElementById("start").value, 10);
                 },
                 getEnd: function () {
                     return parseInt(document.getElementById("end").value, 10);
                 },
                 hiddenCountInput: hCount,
                 hiddenSizesInput: hSizes,
                 hiddenStartsInput: hStarts
             });
         }
 
+        // Stash applyMode on the form so init() can restore last-used mode.
+        form.applyMode = applyMode;
         return form;
     },
 
     init: function () {
         // show a jquery-ui dialog when a user clicks on the 'Add Annotation' button
         let dialog = document.getElementById('myVariantsDialog');
         if (!dialog) {
             dialog = document.createElement("div");
             dialog.id = "myVariantsDialog";
             dialog.style = "display: none";
 
             dialogButtons = {};
             // Call the function to build the form, but only if logged in already
             if (!userIsLoggedIn) {
                 let msg = document.createElement("div");
@@ -1314,128 +1429,147 @@
                     myVariants.createItem(form);
                 };
             }
             dialogButtons.Cancel = function(){
                 $(this).dialog("close");
             };
             $(dialog).dialog({
                 title: "My Annotations",
                 resizable: false,
                 height: "auto",
                 width: 580,
                 modal: true,
                 closeOnEscape: true,
                 autoOpen: false,
                 buttons: dialogButtons,
+                open: function() {
+                    // Restore the last input mode the user picked (or default to HGVS).
+                    let form = document.getElementById("myVariants-form");
+                    if (form && form.applyMode) {
+                        let saved;
+                        try { saved = localStorage.getItem("myVariantsLastMode"); } catch (e) { saved = null; }
+                        const validModes = ["hgvs", "transcript", "snv", "cnv"];
+                        form.applyMode(validModes.indexOf(saved) >= 0 ? saved : "hgvs");
+                    }
+                },
                 close: function() {
                     // Reset block rows so the next open starts clean.
                     let form = document.getElementById("myVariants-form");
                     if (form && form.blocksWidget) {
                         form.blocksWidget.clear();
                     }
                 }
             });
         } else {
-            // got here after async image update, need to update the bed form coordinates
+            // Reopening after an async image update; refresh the position fields.
+            // The dialog's open callback re-applies the last-used mode.
             let form = document.getElementById("myVariants-form");
             let start = form.elements.start;
             let end = form.elements.end;
             let thickStart = form.elements.thickStart;
             let thickEnd = form.elements.thickEnd;
-            start.value = thickStart.value = hgTracks.winStart;
-            end.value = thickEnd.value = hgTracks.winEnd;
-            // Update the position summary to reflect new coordinates
+            if (start) start.value = hgTracks.winStart;
+            if (end) end.value = hgTracks.winEnd;
+            if (thickStart) thickStart.value = hgTracks.winStart;
+            if (thickEnd) thickEnd.value = hgTracks.winEnd;
             let summaryText = document.getElementById("positionSummaryText");
             if (summaryText) {
                 let chromEl = document.getElementById("chrom");
                 let startFmt = parseInt(hgTracks.winStart).toLocaleString();
                 let endFmt = parseInt(hgTracks.winEnd).toLocaleString();
-                summaryText.innerHTML = "<b>Position:</b> " + (chromEl ? chromEl.value : hgTracks.chromName) +
-                    ":" + startFmt + "-" + endFmt + " <span style='color:#888'>(from current view)</span>";
+                summaryText.textContent =
+                    (chromEl ? chromEl.value : hgTracks.chromName) + ":" + startFmt + "-" + endFmt;
             }
         }
 
         // if we clicked outside of the pop up, close the popup:
         document.addEventListener('click', (e) => {
             let dialogEl = document.getElementById("myVariantsDialog");
             if (!dialogEl) return;
             // If the click handler that ran first removed its own target from
             // the document (eg a row remove button, or a Grammarly/extension
             // DOM swap on the description field), e.target is now detached
             // and `.contains(e.target)` would falsely report "outside". Skip.
             if (!document.contains(e.target)) return;
             let dialogContainer = dialogEl.parentElement;
             // Check if click target is inside the dialog (handles native dropdowns that render outside bounds)
             if (dialogContainer && !dialogContainer.contains(e.target)) {
                 $("#myVariantsDialog").dialog("close");
             }
         });
     },
 
     createItem: function(form) {
         // sends a post to hgTracks that adds a new item to the users custom track
         // and updates the image to include this track if it wasn't already there
-        // Strict block check now (server will re-check authoritatively).
-        // noBlocks means the user added a row but left every size empty;
-        // treat as no blocks so the server synthesizes a single full-span block.
+        // Block validation only matters for the transcript path; SNV/CNV don't carry blocks.
         let blockResult = {ok: true, noBlocks: true};
-        if (form.blocksWidget && form.blocksWidget.getRowCount() > 0) {
+        const isTranscript = !!(document.getElementById("typeTranscript") &&
+                                document.getElementById("typeTranscript").checked);
+        if (isTranscript && form.blocksWidget && form.blocksWidget.getRowCount() > 0) {
             blockResult = form.blocksWidget.validate();
             if (!blockResult.ok) {
                 alert("Block error: " + blockResult.msg);
                 return;
             }
         }
         const data = {};
         if (form.elements.hgvsInput.value) {
             data.hgvsInput = form.elements.hgvsInput.value;
         } else {
+            const itemType = isTranscript ? "transcript"
+                : (document.getElementById("typeCnv").checked ? "cnv" : "snv");
             Array.from(form.elements).forEach( (ele) => {
                 if (ele.name === "myVariantsHgvsInput" || ele.name === "hgt_doJsCommand" ||
+                        ele.name === "myVariantsType" ||
                         (ele.tagName !== "INPUT" && ele.tagName !== "TEXTAREA")) {return;}
                 const key = ele.id;
                 let value = ele.value;
                 // Handle Spectrum color picker - get the value from spectrum if available
                 if (ele.id === "color" && $(ele).spectrum) {
                     let spectrumColor = $(ele).spectrum("get");
                     if (spectrumColor) {
                         value = spectrumColor.toHexString();
                     }
                 }
                 data[key] = value;
             });
+            data.itemType = itemType;
+            data.cnvType = (itemType === "cnv") ? document.getElementById("cnvType").value : "";
+            if (itemType === "cnv") data.ref = "";
+            else if (itemType === "transcript") { data.ref = ""; data.alt = ""; }
             // Collect custom fields from the dynamic rows
             let customRows = document.querySelectorAll("#customFieldsList .customFieldRow");
             if (customRows.length > 0) {
                 let customFields = [];
                 customRows.forEach(function(row) {
                     let name = row.querySelector(".customFieldName").value.trim();
                     if (name) {
                         let value = row.querySelector(".customFieldValue").value;
                         customFields.push({name: name, value: value});
                     }
                 });
                 if (customFields.length > 0) {
                     data.extraFields = customFields;
                 }
             }
             // Convert hidden block fields from CSV strings to arrays of ints.
-            // Omit when blockCount is 0, or when the widget reported noBlocks
-            // (rows added but all sizes left empty); server then synthesizes
-            // a single full-span block.
+            // Drop blocks entirely for SNV/CNV; server synthesizes a single full-span
+            // block. For transcript with noBlocks (rows added but every size empty),
+            // also drop and let the server synthesize.
             let bc = parseInt(data.blockCount, 10);
-            if (!bc || blockResult.noBlocks) {
+            if (!isTranscript || !bc || blockResult.noBlocks) {
                 delete data.blockCount;
                 delete data.blockSizes;
                 delete data.chromStarts;
             } else {
                 data.blockCount = bc;
                 data.blockSizes = data.blockSizes.split(",").map(Number);
                 data.chromStarts = data.chromStarts.split(",").map(Number);
             }
         }
 
         // Show loading indicator
         const loadingId = showLoadingImage("imgTbl");
         document.body.style.cursor = "wait";
 
         // Build request - use fetch() instead of form.submit()