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/hgc/hgc.c src/hg/hgc/hgc.c
index 98409105b14..48bfe4d4b8e 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -27516,30 +27516,34 @@
  *   keep   (default) - do nothing, every search's track stays as-is
  *   hide             - leave earlier BLAT tracks in the session but set them to hide
  *   delete           - remove earlier BLAT tracks from the session (their trash files age out)
  * Only BLAT-tagged tracks are touched; the track just made is left alone. */
 char *oldTracks = cfgOptionDefault("blatOldTracks", "keep");
 /* Fail safe on an unrecognized value (e.g. a typo in hg.conf): fall back to "keep" rather than to
  * the destructive "delete" branch below, so a misconfiguration never silently discards a user's
  * previous BLAT tracks. */
 if (differentString(oldTracks, "keep") && differentString(oldTracks, "hide")
     && differentString(oldTracks, "delete"))
     {
     warn("hg.conf blatOldTracks has unrecognized value '%s'; expected keep|hide|delete. "
          "Treating as 'keep'.", oldTracks);
     oldTracks = "keep";
     }
+/* The BLAT search form shows a "Keep results" checkbox when this is set to "delete", letting the
+ * user opt out per search; blatKeepResults is the cart variable it sets. */
+if (cartUsualBoolean(cart, "blatKeepResults", FALSE))
+    oldTracks = "keep";
 if (differentString(oldTracks, "keep"))
     {
     struct customTrack *ct, *next, *keptList = NULL;
     for (ct = ctList; ct != NULL; ct = next)
         {
         next = ct->next;
         if (ct->tdb != NULL && sameOk(trackDbSetting(ct->tdb, "blatResult"), "on"))
             {
             if (sameString(oldTracks, "hide"))
                 {
                 cartSetString(cart, ct->tdb->track, "hide");
                 slAddHead(&keptList, ct);   /* keep it in the session, just hidden */
                 }
             /* "delete": drop it from the list so customTracksSaveCart writes it out */
             }