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/utils/hgConfCatalog/hgConfCatalog.py src/hg/utils/hgConfCatalog/hgConfCatalog.py
index dbb2f248d11..8c6543a9ca5 100755
--- src/hg/utils/hgConfCatalog/hgConfCatalog.py
+++ src/hg/utils/hgConfCatalog/hgConfCatalog.py
@@ -414,30 +414,48 @@
                "the cart.  It comes out once the new form replaces the old "
                "one, which is what makes it a gate rather than a knob."),
         h("blatNewFormBanner", "flag", "hg/hgBlat/hgBlat.c:825",
           default="follows blatNewForm", role="gate", verified=True,
           ticket="37893",
           note="Whether the classic BLAT search form carries a banner offering "
                "the new one.  Defaults to whatever blatNewForm is set to: "
                "where the new form is enabled, a user who took its go-back "
                "link needs a way to return, and where it is disabled there is "
                "nothing to advertise.  Set explicitly to override either way.  "
                "The default is that nested read rather than a literal, so the "
                "sunset report cannot tell from the tree whether this one has "
                "shipped; it ships and goes away with blatNewForm.  Sibling of "
                "blatNewPageBanner, which does the same job for the results "
                "page."),
+        h("sessionNewPage", "flag", "hg/hgSession/hgSession.c", default="FALSE",
+          role="gate", ticket="37996",
+          note="Replaces hgSession's classic C-generated 'My Sessions' page with "
+               "the JavaScript-built one in hg/js/hgSession.js (a save bar plus a "
+               "searchable, sortable session table with inline share/rename/"
+               "overwrite/delete).  Off while the new page is being tested.  Read "
+               "through cartUsualBoolean, so this is only the default: the page's "
+               "banner offers a per-user opt-in/opt-out that sets sessionNewPage "
+               "in the cart.  Sibling of blatNewForm; comes out once the new page "
+               "replaces the old one."),
+        h("sessionNewPageBanner", "flag", "hg/hgSession/hgSession.c",
+          default="follows sessionNewPage", role="gate", ticket="37996",
+          note="Whether the classic and new Sessions pages carry the banner that "
+               "links to the other one.  Defaults to whatever sessionNewPage is "
+               "set to: where the new page is enabled a user who took its go-back "
+               "link needs a way to return, and where it is disabled there is "
+               "nothing to advertise.  Set explicitly to override either way.  "
+               "Sibling of blatNewFormBanner."),
         h("quickLiftClipToChains", "flag", "hg/lib/quickLift.c:231",
           default="TRUE", role="gate", verified=True, ticket="38042",
           note="Whether an item too big for the chains quickLift loaded is "
                "pulled in to what they cover rather than dropped.  Read once, "
                "in quickLiftIntervalsToBedClip, which is the display path; "
                "the details page calls quickLiftIntervalsToBed and always "
                "sees the item's true extent.  Born TRUE, so it never gated a "
                "release: like alwaysItemRgb, the off position is the way the "
                "browser behaved before the change rather than anything about "
                "the machine, so it is a gate and should go once nobody has "
                "asked for the old behaviour."),
     ],
 }