91259a696e598fb4b6807cda9b40eb72971e9209
braney
  Sat Sep 12 06:59:18 2026 -0700
cartTrackVarCatalog: catalog the faceted composite's groupBy, refs #37838

<track>.groupBy says which of a faceted composite's two dimensions is kept
together in the image: "sample" puts one sample's data types side by side,
"dataType" puts the same data type for every sample side by side.  Written by
cartDump from facetedComposite.js and read back in hgTrackUi, both of which
store it only if it is one of those two words, so nothing unvalidated reaches
the <script> block.  trackDb supplies the starting choice with defaultGroupBy
and the cart value wins over it.  Added under #36210.

diff --git src/hg/utils/cartTrackVarCatalog/cartTrackVarCatalog.py src/hg/utils/cartTrackVarCatalog/cartTrackVarCatalog.py
index cd293d5fe79..b4f3cb4763d 100755
--- src/hg/utils/cartTrackVarCatalog/cartTrackVarCatalog.py
+++ src/hg/utils/cartTrackVarCatalog/cartTrackVarCatalog.py
@@ -245,30 +245,44 @@
                    "variable.  Read for a superTrack as well as a composite; "
                    "the surrounding test is tdbIsContainer || "
                    "tdbIsSuperTrack."),
             v("sortOrder", "string", "hg/lib/hui.c:sortOrderGet",
               note="Subtrack table sort, e.g. 'cellType=+ view=-'."),
             v("facetSortOrder", "string", "hg/hgTrackUi/hgTrackUi.c:3314",
               note="Same thing for a faceted composite's table, and the same "
                    "'field=+ field2=-' syntax, which facetedComposite.js:921 "
                    "says it copied from sortOrder above.  Written only by "
                    "JavaScript (facetedComposite.js:931), sent even when empty "
                    "so the server clears a stale value, and read back in "
                    "hgTrackUi to override trackDb's defaultSortField.  The "
                    "read treats it as untrusted, because the JSON it lands in "
                    "goes inside a <script> block: anything outside "
                    "alphanumerics and _ . - + = space drops the whole value."),
+            v("groupBy", "enum", "hg/hgTrackUi/hgTrackUi.c:3355",
+              values=["sample", "dataType"], tdb="defaultGroupBy",
+              note="Which of a faceted composite's two dimensions is kept "
+                   "together in the image: 'sample' puts one sample's data "
+                   "types side by side, 'dataType' puts the same data type for "
+                   "every sample side by side.  Only meaningful when the "
+                   "metadata has data types, since without them a sample is a "
+                   "single track.  Written by cartDump (cartDump.c:88), which "
+                   "gets it from facetedComposite.js:1371 and stores it only if "
+                   "it is one of the two words; hgTrackUi reads it back the "
+                   "same way, so nothing unvalidated reaches the <script> "
+                   "block.  The cart value wins over trackDb's "
+                   "defaultGroupBy.  Added with the Fiber-seq faceted "
+                   "composite work, refs #36210."),
             v("filterComp.<groupTag>", "list", "hg/lib/hui.c:3119", multi=True,
               note="One per ABC dimension of a filterComposite.  'All' means "
                    "every option selected."),
             v("subGroup<n>", "string", "hg/lib/hui.c:2905",
               note="trackDb-side dimension definition, read back when "
                    "rebuilding the matrix."),
         ],
     },
     "view": {
         "what": "A composite view is a middle namespace, not a var of its "
                 "own: any type-layer var can appear as "
                 "<composite>.<view>.<var>.",
         "vars": [
             v("<view>.<anyTypeVar>", "varies",
               "hg/lib/cart.c:cartLookUpVariableClosestToHome",