9f8d33c8b2b6bc61f6d02d781c4e02836f7099f9
max
  Fri Aug 21 02:05:42 2026 -0700
hgSession: new opt-in JavaScript "My Sessions" page; share gbModern.css with hgBlat. refs #38157

Applies the hgBlat facelift strategy (#37996) to hgSession: an opt-in,
client-rendered "My Sessions" page gated by the sessionNewPage /
sessionNewPageBanner hg.conf flags (mirroring blatNewForm / blatNewFormBanner),
with a banner linking between the classic and new pages so neither is a one-way
door. sessionNewPage also flips the site default.

hgSession.c stays the data/action backend: it emits the session list and page
config as an inline JSON global (hgSessionData) into an empty #sessionApp
container, and the new hgSession.js builds the UI - a save-current-view card
(name + optional description + "only I can load it"; empty name saves under a
random share_ name), a "most recently saved session" one-click Update, a
searchable/sortable/paged DataTable of sessions (assembly + position, created
with last-used on hover, views, a lock icon on private sessions), inline Share
(copy link / email / gallery), Edit (rename + description + private), Overwrite
and Delete, and a bulk Select -> Delete-all-selected mode.  The mutating actions
POST to new JSON endpoints (hgS_doDeleteJson / doShareJson / doGalleryJson /
doOverwriteJson / doDescribeJson) that run the same SQL as the classic full-page
handlers and return JSON, so the table updates in place; loads, file up/downloads
and custom-track backup stay as ordinary form submits/links.  The Advanced panel
keeps feature parity with the classic page (load another user's session, load
from URL/file, save to file, back up custom tracks, reset), minus the login/
change-password links that now live in the top menu.

Shared UCSC house-style components (design tokens, .gbPill, .gbCard, .gbStrip,
.gbSection, .gbShareBox, .gbBanner, the .gbModal* dialog and a .gbTable) are
factored into a new gbModern.css.  hgBlat is migrated onto it: its generic
.blat* classes are renamed to the shared .gb* names in hgBlat.css / hgBlat.js
and the #blatResults / #blatFormBox containers get class="gbApp"; verified
pixel-clean against the previous search form and results pages, including the
rename modal.  hgSession.css holds only session-specific layout.

diff --git src/hg/hgBlat/hgBlat.c src/hg/hgBlat/hgBlat.c
index 972bedda3f9..82d35e0a512 100644
--- src/hg/hgBlat/hgBlat.c
+++ src/hg/hgBlat/hgBlat.c
@@ -607,30 +607,31 @@
 return locusConn;
 }
 
 static void printBlatResultsApp(struct psl *pslList, char *database, char *organism, char *browserUrl,
     char *hgcUrl, char *pslName, char *faName, char *customText, char *uiState, char *unhideTrack,
     struct sqlConnection *locusConn, struct subText *subList)
 /* "Table" output mode: emit the hit data as an inline JSON object plus an empty container, and let
  * hgBlat.js build the UI (summary strip, DataTable with identity/coverage bars, detail panel).
  * All presentation lives in hgBlat.js; this function only assembles data.
  * On a fresh search the per-hit "Alignment details" links go to hgc's htcUserAli (which reads the
  * ephemeral trash .pslx/.fa); on a shared-link reopen (pslName NULL) there is no trash, so they go
  * to htcBlatAlign instead, which rebuilds each alignment from the durable bigPsl custom track. */
 {
 struct psl *psl;
 jsIncludeDataTablesLibs();
+webIncludeResourceFile("gbModern.css");   // shared house-style components (.gbPill, .gbCard, ...)
 webIncludeResourceFile("hgBlat.css");
 jsIncludeFile("hgBlat.js", NULL);
 
 struct jsonWrite *jw = jsonWriteNew();
 jsonWriteObjectStart(jw, NULL);
 
 jsonWriteObjectStart(jw, "config");
 /* For assembly/GenArk hubs the internal names carry a "hub_NNN_" prefix; drop it so the Assembly
  * field reads cleanly (and doesn't show the prefix twice), matching the BLAT Results page title. */
 jsonWriteString(jw, "db", trackHubSkipHubName(database));
 jsonWriteString(jw, "organism", trackHubSkipHubName(organism));
 jsonWriteString(jw, "queryName", pslList->qName);
 jsonWriteNumber(jw, "querySize", pslList->qSize);
 jsonWriteNumber(jw, "hitCount", slCount(pslList));
 jsonWriteBoolean(jw, "multiQuery", pslListMultiQuery(pslList));
@@ -780,31 +781,31 @@
                 for (i = 0; i < n; ++i)
                     jsonWriteString(jw, NULL, words[i]);
                 freeMem(dupe);
                 jsonWriteListEnd(jw);
                 }
             }
         sqlFreeResult(&sr);
         }
     jsonWriteObjectEnd(jw);
     freeMem(inTabUrl);
     freeMem(newTabUrl);
     }
 jsonWriteListEnd(jw);    // hits
 jsonWriteObjectEnd(jw);  // root
 
-printf("<div id='blatResults'></div>\n");
+printf("<div id='blatResults' class='gbApp'></div>\n");
 jsInlineF("var hgBlatData = %s;\n", jw->dy->string);
 jsonWriteFree(&jw);
 }
 
 static void printBlatBannerStyle()
 /* Emit the .blatBanner rule for the classic, C-rendered pages.  Those don't load hgBlat.js, which
  * is where the new pages get this class from, so define the same thing here; keep the values in
  * sync with the .blatBanner rule in hgBlat.js.  Only the first call emits anything, so a page may
  * carry more than one banner without repeating the stylesheet. */
 {
 static boolean styleDone = FALSE;
 if (styleDone)
     return;
 styleDone = TRUE;
 printf("<style>"
@@ -2697,33 +2698,34 @@
  * updated by setupGenomeSearchBar itself, and blatFormSetDb() updates the hidden db field and the
  * db-bearing sidebar links in place.  So no submit() - picking a genome no longer reloads. */
 /* setupGenomeSelector is the combobox version of setupGenomeSearchBar - same config, caret inside
  * the field, common assemblies as pills underneath.  Swapping the two function names is the whole
  * change, so the other CGIs on the old widget can move over one at a time. */
 jsInlineF(
     "setupGenomeSelector({\n"
     "    inputId: '%s',\n"
     "    onSelect: function(item) {\n"
     "        blatFormSetDb(dbFromRecentItem(item));\n"
     "    }\n"
     "});\n"
     , searchBarId
 );
 
-printf("<div id='blatFormBox'></div>\n");
+printf("<div id='blatFormBox' class='gbApp'></div>\n");
 printf("</form>\n");
 
+webIncludeResourceFile("gbModern.css");   // shared house-style components (.gbPill, .gbCard, ...)
 webIncludeResourceFile("hgBlat.css");
 jsIncludeFile("hgBlat.js", NULL);
 
 struct jsonWrite *jw = jsonWriteNew();
 jsonWriteObjectStart(jw, NULL);
 jsonWriteString(jw, "db", trackHubSkipHubName(db));
 jsonWriteString(jw, "organism", trackHubSkipHubName(organism));
 /* Shown as the search bar's own contents rather than in a separate "Current genome:" line: the bar
  * is wide enough for the full assembly description, and setupGenomeSearchBar keeps it up to date as
  * the user picks a different one. */
 jsonWriteString(jw, "dbLabel", getCurrentGenomeLabel(db));
 jsonWriteString(jw, "userSeq", cartUsualString(cart, "userSeq", ""));
 blatFormJsonList(jw, "types", typeList, ArraySize(typeList));
 jsonWriteString(jw, "type", cartUsualString(cart, "type", typeList[0]));
 /* Sort and output have no dropdown on this page - hgBlat.js submits them as hidden fields, so the