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/hgc/hgc.c src/hg/hgc/hgc.c
index 8e77cb226f4..99bb876867d 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -18,30 +18,31 @@
 #include "errAbort.h"
 #include "dystring.h"
 #include "nib.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "cart.h"
 #include "jksql.h"
 #include "dnautil.h"
 #include "dnaseq.h"
 #include "fa.h"
 #include "fuzzyFind.h"
 #include "seqOut.h"
 #include "hdb.h"
 #include "spDb.h"
 #include "hui.h"
+#include "hVarSubst.h"
 #include "hgRelate.h"
 #include "htmlPage.h"
 #include "psl.h"
 #include "cogs.h"
 #include "cogsxra.h"
 #include "bed.h"
 #include "cgh.h"
 #include "agpFrag.h"
 #include "agpGap.h"
 #include "ctgPos.h"
 #include "contigAcc.h"
 #include "ctgPos2.h"
 #include "clonePos.h"
 #include "bactigPos.h"
 #include "rmskOut.h"
@@ -3764,32 +3765,37 @@
  * freeze, as indicated by the "origAssembly" trackDb setting */
 {
 trackDbPrintOrigAssembly(tdb, database);
 }
 
 static char *getHtmlFromSelfOrParent(struct trackDb *tdb, char *liftDb)
 /* Get html from self or from parent if not in self. */
 {
 for (;tdb != NULL; tdb = tdb->parent)
     {
     if (sameString(trackHubSkipHubName(tdb->track), "quickLiftChain"))
         tdb->html = hFileContentsOrWarning(hHelpFile(trackHubSkipHubName(tdb->track)));
     if (liftDb && isEmpty(tdb->html))
         tdb->html = getTrackHtml(liftDb, tdb->table);
     if (tdb->html != NULL && tdb->html[0] != 0)
+        {
+        // a hub's description page never went through hgTrackDb, substitute its variables
+        // here.  tdb is the track the html belongs to, so $parentTrack means its container.
+        hVarSubstTrackDbHtml(cart, tdb, database);
         return tdb->html;
         }
+    }
 return NULL;
 }
 
 void printTrackHtml(struct trackDb *tdb)
 /* If there's some html associated with track print it out. Also print
  * last update time for data table and make a link
  * to the TB table schema page for this table. */
 {
 // safety net: a few detail pages never print a position, so flush the iframe here
 printPendingIframe();
 if (!isCustomTrack(tdb->track) && !isMyVariantsType(tdb->type))
     {
     printRelatedTracks(database, trackHash, tdb, cart);
     extraUiLinks(database, tdb, cart);
     printTrackUiLink(tdb);