c7221bad6eda31fe88c6f9880276d8f993ff876e
chmalee
  Thu Mar 5 12:37:14 2026 -0800
Disable recent/popular species list selection in the hgConvert toDb dropdown if there are no chains available to them. Leave them in the list but unselectable, refs #36232

diff --git src/hg/hgConvert/hgConvert.c src/hg/hgConvert/hgConvert.c
index 2b3f701ba0c..2374737ac43 100644
--- src/hg/hgConvert/hgConvert.c
+++ src/hg/hgConvert/hgConvert.c
@@ -152,35 +152,42 @@
 puts("<div style='text-align: center; margin-top: 20px;'>\n");
 cgiMakeButton(HGLFT_DO_CONVERT, "Submit");
 puts("</div>\n");
 
 /* JavaScript initialization for autocomplete with liftOver filtering */
 jsInlineF(
     "let validTargets = new Set();\n"
     "\n"
     "fetch('../cgi-bin/hubApi/liftOver/listExisting?fromGenome=%s')\n"
     "    .then(response => response.json())\n"
     "    .then(data => {\n"
     "        if (data.existingLiftOvers) {\n"
     "            data.existingLiftOvers.forEach(chain => validTargets.add(chain.toDb));\n"
     "        }\n"
     "\n"
-    "        // Filter popular genomes to only those with a liftOver from source\n"
-    "        let dataEl = document.getElementById('%sPopularData');\n"
-    "        if (dataEl) {\n"
-    "            let popular = JSON.parse(dataEl.textContent);\n"
-    "            dataEl.textContent = JSON.stringify(popular.filter(p => validTargets.has(p.db)));\n"
+    "        // Mark recent/popular genomes that don't have a liftOver from source as disabled\n"
+    "        function markInvalidTargets(items) {\n"
+    "            let sourceDb = '%s';\n"
+    "            return items.filter(function(item) {\n"
+    "                return (item.db || item.genome) !== sourceDb;\n"
+    "            }).map(function(item) {\n"
+    "                if (!validTargets.has(item.db || item.genome)) {\n"
+    "                    item.disabled = true;\n"
+    "                    item.disabledReason = 'No liftOver available from source assembly';\n"
+    "                }\n"
+    "                return item;\n"
+    "            });\n"
     "        }\n"
     "\n"
     "        // Custom onServerReply that processes results and filters to valid targets\n"
     "        function processAndFilterResults(result, term) {\n"
     "            let processed = processFindGenome(result, term);\n"
     "            let filtered = processed.filter(item => validTargets.has(item.genome));\n"
     "            if (filtered.length === 0 && processed.length > 0) {\n"
     "                // Found genomes but none have liftOver from source\n"
     "                return [{label: 'No liftOver available for matching genomes', value: '', genome: '', disabled: true}];\n"
     "            } else if (filtered.length === 0) {\n"
     "                // No genomes matched the search at all\n"
     "                return [{label: 'No genomes found', value: '', genome: '', disabled: true}];\n"
     "            }\n"
     "            return filtered;\n"
     "        }\n"
@@ -190,62 +197,62 @@
     "            return [{label: 'No genomes found', value: '', genome: '', disabled: true}];\n"
     "        }\n"
     "\n"
     "        function onGenomeSelect(selectEle, item) {\n"
     "            // Ignore disabled/placeholder items\n"
     "            if (item.disabled || !item.genome) {\n"
     "                return;\n"
     "            }\n"
     "            selectEle.textContent = item.label;\n"
     "            document.mainForm.%s.value = item.commonName.split('(')[0].trim();\n"
     "            document.mainForm.%s.value = item.genome;\n"
     "            document.mainForm.submit();\n"
     "        }\n"
     "\n"
     "        let selectEle = document.getElementById('toGenomeLabel');\n"
-    "        initSpeciesAutoCompleteDropdown('%s', onGenomeSelect.bind(null, selectEle), null, null, processAndFilterResults, onSearchError);\n"
+    "        initSpeciesAutoCompleteDropdown('%s', onGenomeSelect.bind(null, selectEle), null, null, processAndFilterResults, onSearchError, markInvalidTargets);\n"
     "    });\n"
     "\n"
     "document.addEventListener('DOMContentLoaded', () => {\n"
     "    let btn = document.getElementById('%sButton');\n"
     "    if (btn) {\n"
     "        btn.addEventListener('click', () => {\n"
     "            let val = document.getElementById('%s').value;\n"
     "            $('[id=\\x27%s\\x27]').autocompleteCat('search', val);\n"
     "        });\n"
     "    }\n"
     "    let toggle = document.getElementById('%sToggle');\n"
     "    if (toggle) {\n"
     "        let wasOpen = false;\n"
     "        toggle.addEventListener('mousedown', () => {\n"
     "            let $input = $('[id=\\x27%s\\x27]');\n"
     "            wasOpen = $input.autocompleteCat('widget').is(':visible');\n"
     "        });\n"
     "        toggle.addEventListener('click', () => {\n"
     "            let $input = $('[id=\\x27%s\\x27]');\n"
     "            if (wasOpen) {\n"
     "                $input.autocompleteCat('close');\n"
     "            } else {\n"
     "                $input.val('');\n"
     "                $input.autocompleteCat('search', '');\n"
     "                $input.focus();\n"
     "            }\n"
     "        });\n"
     "    }\n"
     "});\n"
     , liftOver->fromDb
-    , searchBarId
+    , liftOver->fromDb
     , HGLFT_TOORG_VAR
     , HGLFT_TODB_VAR
     , searchBarId, searchBarId, searchBarId, searchBarId
     , searchBarId, searchBarId, searchBarId
 );
 
 puts("</FORM>\n");
 
 cartWebEnd();
 }
 
 static double scoreLiftOverChain(struct liftOverChain *chain,
     char *fromOrg, char *fromDb, char *toOrg, char *toDb, struct hash *dbRank )
 /* Score the chain in terms of best match for cart settings */
 {