66ea6cb4eaf2036e464be55b295765ed5105a0fb
max
  Wed Apr 22 09:42:25 2026 -0700
hgTrackUi/hui: render filter UI on supertrack configuration pages

Supertracks (group of tracks with superTrack on, no data of their own)
previously had no way to expose a shared filter: their trackDb stanza
can declare filter.* / filterByRange.* / filterValues.*, but those
settings were never drawn on the supertrack's hgTrackUi page. So users
had to open each subtrack's own configuration page and set the same
filter there, and the "lrSv.filter.svLen" cart namespace went unused.

This change wires that up:

- hgTrackUi.c (superTrackUi): after listing the subtracks, if the
supertrack tdb declares any filter.* settings call scoreCfgUi() to
render the standard filter UI. Cart variables land under the
supertrack's own name (e.g. "lrSv.filter.svLen.min"), and subtracks
already inherit them through cartOptionalStringClosestToHome()
walking tdb->parent. Subtrack-level values continue to override.

- hui.c:
- buildFilterBy() / filterByValues(): tolerate a NULL autoSql object,
so supertracks (which have no data table) don't errAbort when they
declare filterValues.* of virtual aggregated fields. Missing-field
errAbort still fires in the normal subtrack case.
- scoreCfgUi() / cfgByCfgType(): when called with title == NULL (the
supertrack filter path), suppress the default "<p>" separator and
the "<BR>" between the title bar and the filter block; the caller
renders its own section heading.
- asForTdb(): handle conn == NULL by returning NULL rather than
crashing, since supertrack filter rendering has no associated
sqlConnection.

refs #37426

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index d4d3ac60db7..42c89a3ce70 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -2918,30 +2918,51 @@
   "$(this).next().children().removeClass('seg-active');"
   "let labelToFind = capitalizeFirstLetter($(this).val());"
   "$(this).next().find('button').filter(function() { return $(this).text().trim() === labelToFind; }).addClass('seg-active');"
   "});");
 // * Grey out the superTrack dropdown when manually set to hide
 jsInline("$('.superDropdown').on('change', function() {"
   "if ($(this).val() === 'hide') {"
     "$(this).removeClass('normalText').addClass('hiddenText');"
   "} else {"
     "$(this).removeClass('hiddenText').addClass('normalText');"
   "}"
   "});");
 // * Hide all subtrack dropdowns from the user. They are used so the CGI arguments
 // are sent to hgTracks, but are not necessary as UI elements anymore
 jsInline("$('#superTrackTable .vizSelect').hide();");
+
+// --- Supertrack-level filters ---
+// If the supertrack's trackDb declares any filter.*, filterValues.*,
+// filterByRange.*, etc. settings, render the standard filter UI here.
+// The cart variables are stored under the supertrack's name
+// (e.g. "lrSv.filter.svLen.min"). Subtracks inherit these values via
+// cartOptionalStringClosestToHome() during hgTracks rendering; a cart
+// value set on a subtrack always overrides the supertrack's.
+if (bedHasFilters(superTdb))
+    {
+    puts("<h3 style='margin-top:1em'>Filters ");
+    printInfoIcon("Values set here are inherited by every subtrack in this "
+                  "container. Any filter set on an individual subtrack's "
+                  "Track Settings page overrides the value set here for that "
+                  "subtrack only.");
+    puts("</h3>");
+    // Pass title=NULL so scoreCfgUi does not emit its "<p><B>title</B>"
+    // banner. The container <h3> above is already the section label.
+    scoreCfgUi(database, cart, superTdb, superTdb->track,
+               NULL, 1000, /*boxed=*/FALSE);
+    }
 }
 
 #ifdef USE_HAL
 static void cfgHalSnake(struct trackDb *tdb, char *name)
 {
 boolean parentLevel = isNameAtParentLevel(tdb, name);
 if (parentLevel)
     return;
 char *fileName = trackDbSetting(tdb, "bigDataUrl");
 if (fileName == NULL)
      return;
 char *errString;
 int handle = halOpenLOD(fileName, &errString);
 if (handle < 0)
     errAbort("can't open HAL file: %s", fileName);