f04f1b9af06c823602c5e0ecd3b8c2059d7704d3
jcasper
  Thu Aug 27 14:42:31 2026 -0700
Faceted composites now have a max display mode setting instead of a full override
of child track visibilities, with accompanying support for the onlyVisibility setting.  We also
now include a filter block (applied to appropriate child tracks) when those settings are in place.
refs #37662

diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index fe59d53ff79..ad331852541 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -932,30 +932,49 @@
     "hide",
     "dense",
     "full",
     NULL
     };
 static char *pack[] =
     {
     "hide",
     "dense",
     "squish",
     "pack",
     "full",
     NULL
     };
 
+enum trackVisibility tvFromVisOnlySetting(char *visOnly)
+/* Parse an onlyVisibility value.  Deliberately mirrors hTvGetVizArr() below: matching is
+ * case-insensitive, and an unrecognized value falls back to dense.  Do NOT use
+ * hTvFromString[NoAbort]() here - it is case sensitive and quietly answers tvHide for
+ * anything it doesn't know, which would make the track vanish while the vis dropdown
+ * built from the same setting still looked correct. */
+{
+if (sameWord(visOnly,"dense"))
+    return tvDense;
+else if (sameWord(visOnly,"squish"))
+    return tvSquish;
+else if (sameWord(visOnly,"pack"))
+    return tvPack;
+else if (sameWord(visOnly,"full"))
+    return tvFull;
+else
+    return tvDense;
+}
+
 char ** hTvGetVizArr(enum trackVisibility vis, boolean canPack, char* visOnly) 
 /* return a NULL-terminated array of char* with possible track visibilities */
 {
 if (visOnly != NULL)
     {
     if (sameWord(visOnly,"dense"))
         return denseOnly;
     else if (sameWord(visOnly,"squish"))
         return squishOnly;
     else if (sameWord(visOnly,"pack"))
         return packOnly;
     else if (sameWord(visOnly,"full"))
         return fullOnly;
     else /* default when not recognized */
         return denseOnly;
@@ -964,38 +983,43 @@
     {
     if (canPack)
         return pack;
     else
         return noPack;
     }
 }
 
 void hTvDropDownClassVisOnlyAndExtraWithLabel(char *varName, enum trackVisibility vis,
 				 boolean canPack, char *class, char *visOnly, struct slPair *events,
 				 char *label)
 // Make track visibility drop down for varName with style class, optional aria-label,
 // and potentially limited to visOnly
 {
 char** vizArr = hTvGetVizArr(vis, canPack, visOnly);
-char* checked = vizArr[vis];
 int vizArrLen = arrNullLen(vizArr);
+char* checked;
 
 // Same as hTvDropDownClassWithJavascript():
 // Normal track with no special limits needs mapping to get back checked value
 static int packIx[] = {tvHide,tvDense,tvSquish,tvPack,tvFull};
-if (visOnly==NULL && canPack)
+if (visOnly != NULL)
+    // Just hide and the one allowed vis, so a tv enum would index off the end of the array
+    checked = (vis == tvHide ? vizArr[0] : vizArr[1]);
+else if (canPack)
     checked = vizArr[packIx[vis]];
+else
+    checked = vizArr[vis];
 
 cgiMakeDropListClassWithIdStyleJavascriptAndLabel(varName, NULL, vizArr, vizArrLen, checked, class, TV_DROPDOWN_STYLE, events, label);
 }
 
 void hTvDropDownClassVisOnlyAndExtra(char *varName, enum trackVisibility vis,
 				 boolean canPack, char *class, char *visOnly, struct slPair *events)
 // Make track visibility drop down for varName with style class, and potentially limited to visOnly
 {
 hTvDropDownClassVisOnlyAndExtraWithLabel(varName, vis, canPack, class, visOnly, events, NULL);
 }
 
 void hideShowDropDownWithClassExtraAndLabel(char *varName, char *id, boolean show, char *class,
                                             struct slPair *events, char *ariaLabel)
 // Make hide/show dropdown for varName with optional aria-label
 {
@@ -9999,39 +10023,73 @@
     }
 return vis;
 }
 
 enum trackVisibility tdbVisLimitedByAncestors(struct cart *cart, struct trackDb *tdb,
                                               boolean checkBoxToo, boolean foldersToo)
 // returns visibility limited by ancestry.
 // This includes subtrack vis override and parents limit maximum.
 // cart may be null, in which case, only trackDb settings (default state) are examined
 // checkBoxToo means ensure subtrack checkbox state is visible
 // foldersToo means limit by folders (aka superTracks) as well.
 {
 boolean subtrackOverride = FALSE;
 enum trackVisibility vis = tdbLocalVisibility(cart,tdb,&subtrackOverride);
 
+// Children of a faceted composite are heterogeneous enough that one inherited vis won't
+// do, so they keep a display mode of their own and the parent's vis is only a ceiling.
+boolean facetedChild = (tdbIsContainerChild(tdb) && tdbIsFacetedComposite(tdb->parent));
+char *onlyVis = (facetedChild ? trackDbLocalSetting(tdb, "onlyVisibility") : NULL);
+
 if (tdbIsContainerChild(tdb))
     {
+    if (facetedChild)
+        {
+        // A child of a faceted composite holds a display mode of its own rather than
+        // inheriting the parent's.  NOTE: tdb->visibility can't tell "asked for something"
+        // from "inherited a default", since trackDbFieldsFromSettings() fills it through
+        // the inheriting trackDbSetting() - hence trackDbLocalSetting here.
+        boolean hasOwnVis = (subtrackOverride
+                             || trackDbLocalSetting(tdb, "visibility") != NULL);
+        if (onlyVis != NULL)
+            {
+            // onlyVisibility pins the child to a single mode, but asking to hide still hides
+            if (!(hasOwnVis && vis == tvHide))
+                vis = tvFromVisOnlySetting(onlyVis);
+            }
+        else if (!hasOwnVis)
+            vis = tvFull;    // No mode of its own, so take whatever the parent allows
+        }
     // subtracks without explicit (cart) vis but are selected, should get inherited vis
-    if (!subtrackOverride)
+    else if (!subtrackOverride)
         vis = tvFull;
     // subtracks with checkbox that says no, are stopped cold
     if (checkBoxToo && !fourStateVisible(subtrackFourStateChecked(tdb,cart)))
         vis = tvHide; // Checkbox says no
     }
+if (facetedChild)
+    {
+    // Note this skips the subtrackOverride shortcut below on purpose: escaping the
+    // parent's limit is exactly the behavior a faceted composite doesn't want.
+    if (vis == tvHide)
+        return tvHide;
+    enum trackVisibility maxVis = tdbVisLimitedByAncestors(cart,tdb->parent,checkBoxToo,
+                                                           foldersToo);
+    if (onlyVis != NULL)  // A pinned child either fits under the maximum or doesn't draw
+        return (tvCompare(vis,maxVis) >= 0 ? vis : tvHide);
+    return tvMin(vis,maxVis);
+    }
 if (subtrackOverride)
     return vis;
                                                             // aka superTrack
 if (vis == tvHide || tdb->parent == NULL || (!foldersToo && tdbIsFolder(tdb->parent)))
     return vis; // end of line
 
 return tvMin(vis,tdbVisLimitedByAncestors(cart,tdb->parent,checkBoxToo,foldersToo));
 }
 
 char *compositeViewControlNameFromTdb(struct trackDb *tdb)
 // Returns a string with the composite view control name if one exists
 {
 char *stView   = NULL;
 char *name     = NULL;
 char *rootName = NULL;