ed65d23618ec85f58df1bc341199e904fab55929
lrnassar
  Fri Sep 18 00:40:10 2026 -0700
hgTrackUi: substitute description page variables once, and in the container excerpt too. refs #38283

Removes the hVarSubstTrackDbHtml call in doMiddle. trackUi already calls it at each
of the two places that go on to print tdb->html, each after its own quickLift
getTrackHtml swap, so the doMiddle call was a second pass over the same text. That
second pass ate the escape in $$db, which reached the reader as hg38 in hgTrackUi
while hgc correctly showed $db. The plain ajax path returns before any html is
printed, so it loses nothing.

The Description panel at the top of a subtrack's settings page shows an excerpt of
its container's page, and nothing had substituted that one. A hub page using ${db}
or ${organism} in its opening paragraph showed the text raw there while the same
page rendered correctly further down the screen.

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index 566c540ee23..711f96846c5 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -3738,30 +3738,33 @@
 // show super-track info
 struct trackDb *tdbParent = tdb->parent;
 
 printf("<b>Configure track container: "
            "<img height=12 src='../images/ab_up.gif'>"
             "<a href='%s?%s=%s&db=%s&c=%s&g=%s'>%s </a></b>",
             hgTrackUiName(), cartSessionVarName(), cartSessionId(cart),
             database, chromosome, cgiEncode(tdbParent->track), tdbParent->longLabel);
 printf("<p>");
 
 if (tdbIsFacetedComposite(tdb))
     return;
 
 if (tdbParent->html)
     {
+    // the excerpt below is the container's own description page, so it needs the same
+    // substitution the track's page gets further down
+    hVarSubstTrackDbHtml(cart, tdbParent, database);
     // collapsed panel for Description
     printf("<p><table>");  // required by jsCollapsible
     jsBeginCollapsibleSectionFontSize(cart, tdb->track, "superDescription", "Description", FALSE,
                                             "medium");
     // TODO: better done with regex
     char *html = replaceChars(tdbParent->html, "<H", "<h");
     html = replaceChars(html, "</H", "</h");
 
     // remove Description header
     html = replaceChars(html, "<h2>Description</h2>", "");
     html = replaceChars(html, "<h3>Description</h3>", "");
     html = replaceChars(html, "<h1>Description</h1>", "");
 
     // remove everything after Description text
     char *end = stringIn("<h2>", html);
@@ -4712,35 +4715,30 @@
 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);
     }
 
-// resolve $hgsid, which hgTrackDb had no cart to resolve, and for a hub the rest of its
-// description page variables: a hub page never went through hgTrackDb at all.  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);
     if (tdb->parent)