7be0d4bee5fc92c426382f480e06254e60f8bb9e
max
  Sat Jul 11 20:15:05 2026 -0700
Cache-bust menu-bar CSS/JS on static pages, refs #10138

hgMenubar.c emitted nice_menu.css and topLinks.js as bare URLs with no version
query, so browsers kept serving a stale cached nice_menu.css after commit
c5a326f3 added the top-right links. The new #trToggle hamburger is hidden by a
rule in that CSS, so on static pages (e.g. the homepage) it showed on first load
and only disappeared after a manual force-reload.

Append ?v=<file mtime> to those two URLs, the same idea CGIs get from
webTimeStampedLinkToResource (which can't be reused here: it emits a
../-relative URL that is wrong for the arbitrary-depth static pages this menu bar
is SSI-included into). Uses the existing fileModTime(), guarded by fileExists so
a missing resource degrades to ?v=0 rather than aborting the menu bar.

diff --git src/hg/hgMenubar/hgMenubar.c src/hg/hgMenubar/hgMenubar.c
index f3fcc977176..2302170fca7 100644
--- src/hg/hgMenubar/hgMenubar.c
+++ src/hg/hgMenubar/hgMenubar.c
@@ -1,28 +1,29 @@
 /*
  * This CGI is used by static html pages to show a menu bar.
  * On an Apache with activated SSI, a html statement like
  * <!--#include virtual="../cgi-bin/hgMenubar"-->
  * will include the menu bar into a static page.
  */
 #include "common.h"
 #include "cheapcgi.h"
 #include "dystring.h"
 #include "filePath.h"
 #include "linefile.h"
 #include "jsHelper.h"
 #include "wikiLink.h"
+#include "portable.h"
 
 #define CGI_NAME "cgi-bin/hgMenubar"
 #define NAVBAR_INC_PATH "/inc/globalNavBar.inc"
 #define OLD_HREF "href=\"../"
 
 char* errMessage;
 
 char *loginLinkHtml()
 /* Return HTML <li> for the top-right Login menu item, or "" if no login system is configured.
  * Static-page variant: the logged-out link uses href="../cgi-bin/hgSession" so the caller's
  * OLD_HREF substitution rewrites it to a page-relative path; logged-in account-dialog URLs come
  * from wikiLink and are absolute.  topLinks.js turns the logged-in item into a dialog. */
 {
 if (!(loginSystemEnabled() || wikiLinkEnabled()))
     return cloneString("");
@@ -43,56 +44,69 @@
     dyStringPrintf(dy, "<a class='topRightLink' href='#' id='loginLink' "
         "title='Account info and sign out' "
         "data-username=\"%s\" data-logouturl=\"%s\" data-changepwurl=\"%s\">%s</a>",
         userName, logoutUrl, changePwUrl ? changePwUrl : "", userName);
     }
 return dyStringCannibalize(&dy);
 }
 
 char *incFilePath(char *cgiPath, char *filePath, char *docRoot)
 /* Replace CGI_NAME in cgiPath with docRoot/filePath.  filePath must begin with "/" eg "/inc/..." */
 {
 char *incPath = replaceChars(cgiPath, "/"CGI_NAME, filePath);
 return catTwoStrings(docRoot, incPath);
 }
 
-void printIncludes(char* baseDir)
+void printIncludes(char* baseDir, char *docRoot)
 {
+// Cache-buster for the menu-bar CSS/JS: append ?v=<file mtime> so browsers refetch these when
+// they change instead of serving a stale cached copy (the CGIs get this from
+// webTimeStampedLinkToResource, but that emits its own "../"-relative URL which is wrong for the
+// arbitrary-depth static pages this menu bar is included into, so we reuse just its mtime idea).
+// fileExists guards fileModTime, which would otherwise abort the menu bar on every static page if
+// a resource were missing.
+char jsPath[PATH_LEN], cssPath[PATH_LEN];
+safef(jsPath, sizeof jsPath, "%s/js/topLinks.js", docRoot);
+safef(cssPath, sizeof cssPath, "%s/style/nice_menu.css", docRoot);
+long jsVer = fileExists(jsPath) ? (long)fileModTime(jsPath) : 0;
+long cssVer = fileExists(cssPath) ? (long)fileModTime(cssPath) : 0;
+
 printf ("<noscript><div class='noscript'><div class='noscript-inner'><p><b>JavaScript is disabled in your web browser</b></p><p>You must have JavaScript enabled in your web browser to use the Genome Browser</p></div></div></noscript>\n");
 printf ("<script type='text/javascript' SRC='%sjs/jquery.js'></script>\n", baseDir);
 printf ("<script type='text/javascript' SRC='%sjs/jquery.plugins.js'></script>\n", baseDir);
 printf("<script type='text/javascript' SRC='%s/js/utils.js'></script>\n", baseDir);
-printf("<script type='text/javascript' SRC='%s/js/topLinks.js'></script>\n", baseDir);
-printf ("<LINK rel='STYLESHEET' href='%sstyle/nice_menu.css' TYPE='text/css'>\n", baseDir);
+printf("<script type='text/javascript' SRC='%s/js/topLinks.js?v=%ld'></script>\n", baseDir, jsVer);
+printf ("<LINK rel='STYLESHEET' href='%sstyle/nice_menu.css?v=%ld' TYPE='text/css'>\n", baseDir,
+    cssVer);
 }
 
 void printMenuBar(char *cgiPath, char *docRoot, char *pagePath, char *filePath)
 {
 char *navBarLoc = incFilePath(cgiPath, filePath, docRoot);
 struct lineFile *menuFile = lineFileOpen(navBarLoc, TRUE);
 char* oldLine = NULL;
 int lineSize = 0;
 
 char *cgiContainerPath = replaceChars(cgiPath, CGI_NAME, "");
 char *newPath = makeRelativePath(pagePath, cgiContainerPath);
 
 char *newHref = catTwoStrings("href=\"", newPath);
 
 printf ("Content-type: text/html\r\n\r\n");
 
 if (sameString(filePath, NAVBAR_INC_PATH))
-    printIncludes(newPath);
+    printIncludes(newPath, docRoot);
 
 while (lineFileNext(menuFile, &oldLine, &lineSize))
     {
     // Not quite as robust as perl search and replace - no variable whitespace handling
     // Also lots of memory leakage - every line is reallocated and forgotten
     char *line = oldLine;
     // Fill the top-right link placeholders.  Login shows the user or a link to hgSession;
     // the Share-a-link button is browser-only, so it is dropped on static pages.
     if (stringIn("<!-- LOGIN_LINK -->", line))
         line = replaceChars(line, "<!-- LOGIN_LINK -->", loginLinkHtml());
     if (stringIn("<!-- SHARE_LINK -->", line))
         line = replaceChars(line, "<!-- SHARE_LINK -->", "");
     char *newLine = replaceChars(line, OLD_HREF, newHref);
     printf("%s\n", newLine);
     }