c0e8fa6df3a0bd406c4188d49ee00f20aef203e5
max
  Mon Sep 7 12:07:18 2026 -0700
Substitute trackDb variables in hub track description pages

A hub's description page comes straight off the hub's web server and has
never been through variable substitution, so a $db or $parentTrack in it
reached the reader as literal text. Native trackDb pages are fine, since
hgTrackDb substitutes them when it loads trackDb, but there was no
equivalent step for a hub.

hgc's getTrackHtml and hgTrackUi's trackUi both call hVarSubstTrackDbHtml
on a hub track's html.  Only a short list of variables is recognized there and nothing is an
error, because a hub page written before this existed can easily contain
a dollar sign inside a shell example, and silently rewriting that would
be worse than not substituting at all.

Adds $parentTrack, the name of the container a track sits in, which is
what a subtrack description page needs to link back to its superTrack or
composite. Views are skipped, since a view has no page of its own, and
the hub_<id>_ prefix is kept so the name works as hgTrackUi's g=
parameter. Documents $track, $parentTrack and $hgsid in trackDb/README.

refs #37599

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index 93b76d607be..cccc3d87ba5 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -2,30 +2,31 @@
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "linefile.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "jksql.h"
 #include "jsHelper.h"
 #include "trackDb.h"
 #include "hgTrackUi.h"
 #include "quickLift.h"
 #include "hdb.h"
 #include "hCommon.h"
 #include "hui.h"
+#include "hVarSubst.h"
 #include "fileUi.h"
 #include "ldUi.h"
 #include "snpUi.h"
 #include "snp125Ui.h"
 #include "snp125.h"
 #include "sample.h"
 #include "wiggle.h"
 #include "hgMaf.h"
 #include "obscure.h"
 #include "chainCart.h"
 #include "chainDb.h"
 #include "gvUi.h"
 #include "grp.h"
 #include "oregannoUi.h"
 #include "chromGraph.h"
@@ -4669,30 +4670,33 @@
 if (tdb == NULL)
    {
    errAbort("Can't find %s in track database %s chromosome %s",
 	    track, database, chromosome);
    }
 
 // Do  little more dupe handling - make a tdb for dupe if any 
 if (isDup)
     {
     struct dupTrack *dup = dupTrackFindInList(dupList, dupWholeName);
     if (dup == NULL)
         errAbort("Can't find duplicate track %s", dupWholeName);
     tdb = dupTdbFrom(tdb, dup);
     }
 
+// A hub's description page never went through hgTrackDb, so its variables are substituted
+// here instead.  This is what lets a hub page link to its container with $parentTrack.
+hVarSubstTrackDbHtml(cart, tdb, database);
 
 if(cartOptionalString(cart, "ajax"))
     {
     // html is going to be used w/n a dialog in hgTracks.js so serve up stripped down html
     // still need CSP2 header for security
     printf("%s", getCspMetaHeader());
     trackUi(tdb, tdbList, ct, TRUE);
     cartRemove(cart,"ajax");
     jsInlineFinish();
     }
 else
     {
     // htmlNoEscape() below lets the <span> through, so the labels themselves have to be
     // escaped here - they come from trackDb, which a track hub controls
     struct dyString *title = dyStringNew(0);