cee097b75fe4d1b29f0debd8af938ba6bf729d53
max
  Tue Aug 11 08:18:42 2026 -0700
hgBlat: new client-rendered BLAT search form and sortable results page, refs #37893

An opt-in modern alternative to the classic hyperlink output, gated by the
blatNewForm / blatNewPage hg.conf flags. hgBlat.c emits the hit data and form
config as inline JSON (hgBlatData / hgBlatFormData) into empty containers, and
hgBlat.js builds the UI: the search form, a sortable/filterable DataTable with
identity and query-coverage bars, a docked per-hit detail panel, a FASTA query
viewer, and share-link and rename-track modals.

- hgBlat.css: all styling for both pages, loaded via webIncludeResourceFile
(not injected from JS); layout uses CSS classes rather than inline styles.
- utils.js: setupGenomeSelector, the combobox genome picker the form uses.
- hgc.c: blatOldTracks / blatKeepResults - the "Keep results" checkbox lets a
user accumulate BLAT result tracks instead of each search replacing the last.
- hgConfCatalog: register the blatNewForm / blatNewFormBanner gates.
- blatExample.fa: the example query the form loads on demand.

Markup is built in JS (escaped values via htmlEncode, everything else set through
DOM properties); no HTML strings in the C or a template engine.

diff --git src/hg/utils/hgConfCatalog/hgConfCatalog.py src/hg/utils/hgConfCatalog/hgConfCatalog.py
index 49d266929ee..d97abd8957f 100755
--- src/hg/utils/hgConfCatalog/hgConfCatalog.py
+++ src/hg/utils/hgConfCatalog/hgConfCatalog.py
@@ -569,31 +569,53 @@
                "are, so a knob; it gates no feature and there is nothing to "
                "flip."),
         h("blatOldTracks", "internal", "hg/hgc/hgc.c:27433", default="keep",
           verified=True,
           note="What happens to the custom tracks left behind by a user's "
                "earlier BLAT searches when a new search makes another one.  "
                "Three values: keep (the default) leaves every one of them "
                "alone, hide leaves them in the session but sets them to hide, "
                "delete drops them from the session and lets their trash files "
                "age out.  Only tracks tagged blatResult=on are touched, and "
                "the track the current search just made is always left alone.  "
                "A string rather than a flag, so it carries no gate/knob role, "
                "but it is a knob in spirit: which of the three a site wants "
                "depends on whether its users run many searches in a session "
                "and get confused about which results are current, and that "
-               "does not stop being a question after a release."),
+               "does not stop being a question after a release.  When set to "
+               "delete, the BLAT search form shows a Keep results checkbox so "
+               "a user can opt out for one search (cart variable "
+               "blatKeepResults, read back in hg/hgc/hgc.c)."),
+        h("blatNewForm", "gate", "hg/hgBlat/hgBlat.c:2913", default="off",
+          verified=True,
+          note="Replaces hgBlat's classic C-generated search form with the "
+               "JavaScript-built one in hg/js/hgBlat.js, which shares its "
+               "styling and page header with the new BLAT results page.  Off "
+               "while the new form is being tested.  Read through "
+               "cartUsualBoolean, so this is only the default: the form's "
+               "banner offers a per-user opt-out that sets blatNewForm=0 in "
+               "the cart.  A gate - it comes out once the new form replaces "
+               "the old one."),
+        h("blatNewFormBanner", "gate", "hg/hgBlat/hgBlat.c:800",
+          default="follows blatNewForm", verified=True,
+          note="Whether the classic BLAT search form carries a banner offering "
+               "the new one.  Defaults to whatever blatNewForm is set to: "
+               "where the new form is enabled, a user who took its go-back "
+               "link needs a way to return, and where it is disabled there is "
+               "nothing to advertise.  Set explicitly to override either way.  "
+               "Sibling of blatNewPageBanner, which does the same job for the "
+               "results page."),
     ],
 }
 
 
 # ---------------------------------------------------------------------------
 # database connections and profiles
 # ---------------------------------------------------------------------------
 
 DATABASE = {
     "what": "Where the CGIs find MySQL.  A profile is a prefix; see the "
             "profile suffix family for the settings legal under any of them.",
     "vars": [
         h("db.host", "profile", "hg/qaPushQ/qaPushQ.c:2435", public=True,
           verified=True, env="HGDB_HOST", family="db",
           note="The main assembly database server."),