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
@@ -1,2754 +1,2772 @@
#!/usr/bin/env python3
"""hgConfCatalog.py - the registry of hg.conf variables.
Third of the browser configuration inventories, after cartTrackVarCatalog.py
(#37838, track-scoped cart variables) and urlCommandCatalog.py (#37923, CGI URL
parameters). This one covers hg.conf: the per-machine configuration file every
CGI reads at startup through the cfgOption* accessors in hg/lib/hgConfig.c.
What makes hg.conf different from the other two, and the reason this file
needed a mechanism the others do not have:
A cart variable belongs to a user and a URL parameter belongs to a request.
An hg.conf variable belongs to a machine, and a machine we do not control.
Mirrors, the GBiB and the GBiC all carry their own hg.conf, so a variable is
reachable long after the tree stops caring about it, and deleting one is a
compatibility decision rather than a cleanup.
On top of that, the tree deliberately manufactures short-lived hg.conf
variables as part of the release process. A user-visible feature is expected
to ship dark behind cfgOptionBooleanDefault(name, FALSE) so it can sit on
master through QA without blocking the release train, then have its default
flipped to TRUE once it is released, then have the flag deleted. That last
step is the one nobody does. Nothing in the tree records that a flag was
meant to be temporary, so shipped gates accumulate: showTutorial has been
defaulting TRUE since v466, greyBarIcons since v492, and both are still
branch points in hgTracks today.
So the registry separates two populations that look identical in the source:
gate introduced to gate a release. Temporary by intent. Has a lifecycle
(added, flipped, sunset) and is expected to be deleted.
knob a genuine deployment switch a mirror is entitled to set forever.
isGbib, browser.dumpStack and hgta.disableAllTables are knobs. These
are exempt from sunsetting and saying so explicitly is what keeps the
report from crying wolf.
The distinction cannot be made mechanically, which is why it is curated here
rather than harvested. Everything else about a gate's lifecycle is mechanical:
harvestHgConf.py --age dates each variable's first read, and each boolean
flag's first TRUE default, out of the git history, mapped onto the CGI_VERSION
in effect at the time. So `added` and `flipped` come from the tree and only
`sunset` is a judgement call. A gate with no explicit sunset gets
flipped + KEEP_AFTER_FLIP, which is the release after mirrors have had a cycle
to object.
Sunset policy, all in versions (releases are about three weeks apart):
KEEP_AFTER_FLIP = 4 once the default is TRUE the feature is public. Keep
the flag four more releases so a mirror or hgwbeta can
switch it back off without a code change, then delete
it along with every branch that reads it.
QA_GRACE = 6 a gate still defaulting FALSE this long after it landed
is not being gated, it is being forgotten. Either turn
it on or delete the feature.
Verification status. Rows carry verified=True only where the classification
was confirmed by reading the code at the cited file:line. --check counts what
is left, because a variable described as a permanent knob when it is really a
forgotten gate defeats the purpose of the exercise.
Usage:
hgConfCatalog.py --json out.json
hgConfCatalog.py --html out.html
hgConfCatalog.py --check # counts and internal consistency
hgConfCatalog.py --reconcile # diff the catalog against the tree
hgConfCatalog.py --reconcile --verbose # ... with the standing drift
hgConfCatalog.py --sunset # what should be deleted, and when
hgConfCatalog.py --sunset-new # ... only what changed since the backlog
hgConfCatalog.py --update-baseline # accept the current backlog
hgConfCatalog.py --refresh --sunset-new # ... rebuilding the age cache
hgConfCatalog.py --cache /tmp/ages.json --refresh --sunset-new
# ... without writing into the tree
--reconcile is the mode meant for a nightly cron: it prints nothing and exits 0
when the tree holds no setting the catalog has not classified, and exits 1 with
the list when somebody has added one. --sunset-new is the same shape for the
release side, and is what belongs in the weekly build's wrap-up: it reports the
gates that went overdue or stalled since the backlog file was last accepted, and
the ones that were cleaned up, and says nothing about the standing backlog.
--sunset itself reports every overdue gate on every run whether or not anything
changed. That is the right thing for a person working the list down and the
wrong thing for anything automated, which is why it is a separate mode.
"""
import argparse
import html
import json
import os
import re
import sys
# Sunset policy, in releases. See the module docstring.
KEEP_AFTER_FLIP = 4
QA_GRACE = 6
# The gates already known to be overdue or stalled, so --sunset-new can report
# what changed rather than the whole standing list. Regenerate with
# --update-baseline and commit it; the diff is then the release-to-release
# history of the backlog, in the git log rather than in somebody's memory.
BACKLOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"hgConfGateBacklog.txt")
# Floor on how many settings a working scan finds; well under the real count.
# See the check in reconcile().
MIN_TREE_NAMES = 150
REDMINE = "https://redmine.gi.ucsc.edu/issues/%d"
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
def h(name, kind, src, default=None, note=None, public=False, verified=False,
role=None, sunset=None, env=None, deprecated=False, family=None,
required=False, ticket=None, debatable=None):
"""One catalog entry.
name the hg.conf setting name
kind what sort of setting: path, table, profile, credential, url,
email, limit, flag, branding, debug, internal, dead
src file:line where the tree reads it
default compiled-in default if the read supplies one
role for boolean flags only: "gate" (a release gate, temporary by
intent, subject to sunsetting) or "knob" (a permanent
deployment switch, exempt). Every cfgOptionBooleanDefault
flag in the tree must be one or the other; --reconcile
enforces that
sunset release by which a gate should be gone from the tree. Omit to
take the default of flipped + KEEP_AFTER_FLIP; the age cache
supplies the flip version
public documented for mirror operators in product/ex.hg.conf, or
belongs there
verified True if the classification was confirmed by reading src
env environment variable that overrides it, via cfgOptionEnv
family groups members of one prefix family, for the docs
required read with cfgVal, so the CGI dies if it is absent
deprecated the feature it configures is gone or going
ticket Redmine ticket that introduced or tracks it
debatable why this row's gate-or-knob call could reasonably go the other
way. Set it rather than picking a side quietly: the whole
point of separating the two is that someone has decided, and a
decision nobody argued with is not the same as a decision.
--check lists these as the review agenda
"""
d = {"name": name, "kind": kind, "src": src, "public": public,
"verified": verified}
for key, val in (("default", default), ("note", note), ("role", role),
("sunset", sunset), ("env", env), ("family", family),
("ticket", ticket), ("debatable", debatable)):
if val is not None:
d[key] = val
if required:
d["required"] = True
if deprecated:
d["deprecated"] = True
return d
# ---------------------------------------------------------------------------
# how hg.conf is read at all
# ---------------------------------------------------------------------------
ACCESSORS = {
"cfgOption": "Returns the value or NULL. Absent means off.",
"cfgOptionDefault": "Returns the value or a compiled-in default.",
"cfgOptionBooleanDefault":
"Boolean with a compiled-in default. Accepts yes/no, on/off, "
"true/false. This is the accessor used to gate a release.",
"cfgVal": "Returns the value or errAborts. The CGI will not run without it.",
"cfgOptionEnv":
"Environment variable first, then hg.conf. Note the argument order: "
"the environment name comes first and is not an hg.conf setting.",
"cfgOptionEnvDefault": "As cfgOptionEnv, with a compiled-in default.",
"cfgOption2":
"Reads prefix.suffix. The prefix is usually a runtime value, which is "
"how one call site serves db.host, central.host and every other "
"profile at once.",
"cfgOptionDefault2": "As cfgOption2, with a compiled-in default.",
}
BOUNDARY = (
"hg.conf is read once per CGI invocation and is never written by the "
"browser. It is not user state: nothing in a session, a saved session or "
"a URL can change it, which is exactly why it is the right place to gate "
"a feature during a release. A mirror's copy is outside our control, so "
"removing a variable has to be treated as an interface change, not a "
"cleanup. Precedence for a value is: the environment (only for the "
"cfgOptionEnv settings, and only where the CGI allows it), then hg.conf, "
"then the compiled-in default."
)
PROFILE_SUFFIXES = {
"what":
"A database profile is a set of hg.conf settings sharing one prefix, "
"read through cfgOption2(profileName, suffix) where profileName is a "
"runtime value. So these suffixes are legal under any profile prefix, "
"and none of the resulting names appears as a literal anywhere in the "
"tree. This is why product/ex.hg.conf documents "
"archivecentral.password while a search of the source finds nothing.",
"src": "hg/lib/jksql.c:231",
"suffixes": ["host", "port", "socket", "user", "password", "db",
"verifyServerCert", "ca", "caPath", "cert", "key", "cipher",
"crl", "crlPath"],
"knownProfiles": ["db", "central", "cart", "customTracks", "archivecentral",
"backupcentral", "myStuff", "myGenome", "rrcentral", "pq",
"rtdb", "cdw"],
}
# ---------------------------------------------------------------------------
# release gates: boolean flags that exist to hold a feature back
# ---------------------------------------------------------------------------
# These are the reason this catalog has a sunset mode. Each was added so a
# user-visible change could sit on master without shipping. Ordered by age so
# the backlog is visible at a glance.
RELEASE_GATES = {
"what": "Boolean flags introduced to ship a feature dark during a release. "
"Temporary by intent: each should be deleted once the feature it "
"guards is public and mirrors have had a cycle to object.",
"vars": [
h("showMouseovers", "flag", "hg/hgTracks/config.c:704", default="FALSE",
role="gate", verified=True,
note="Mouseover text on track items instead of the browser's own "
"title tooltips. Added v446 and still defaulting FALSE, which "
"makes it the oldest gate in the tree that never shipped. Four "
"call sites across config.c and imageV2.c. Either the feature "
"is wanted, in which case flip it, or it is not, in which case "
"the flag and the code behind it should go."),
h("storeUserFiles", "flag", "hg/hgHubConnect/hgHubConnect.c:1741",
default="FALSE", role="gate", verified=True,
note="Hub space, the user file store behind hgHubConnect's upload "
"wizard. Added v447 and briefly defaulted TRUE around v454 "
"before going back to FALSE, so the history shows a flip that "
"was reverted. Four call sites."),
h("hgSession.shortLink", "flag", "hg/hgSession/hgSession.c:179",
default="FALSE", role="gate", verified=True,
note="Short session links. Added v374 and never flipped, which is "
"the longest-running dark feature here."),
h("showHubApiKey", "flag", "hg/hgHubConnect/hgHubConnect.c:580",
default="FALSE", role="gate", verified=True,
note="Expose the hub API key UI. Shares its call site with "
"storeUserFiles, so the two should be retired together."),
h("autoBlatBigPsl", "flag", "hg/hgBlat/hgBlat.c:3375",
default="FALSE", role="gate", verified=True, ticket="32751",
note="Always create a custom track from BLAT results, so a result "
"page can be reopened and shared. The read at hgBlat.c:2972 "
"overrides the file-scope autoBigPsl, which is initialised "
"FALSE at hgBlat.c:60 under the comment \"DEFAULT VALUE change "
"to TRUE in future\", and eleven branches downstream test it. "
"Filed as a knob until that was read, on the strength of the "
"default being an identifier the harvester could not resolve; "
"a flag whose own source says to flip it later is a gate."),
h("blatShowLocus", "flag", "hg/hgBlat/hgBlat.c:1164", default="FALSE",
role="gate", verified=True,
note="Show the genomic locus alongside BLAT results."),
h("modernAlignPage", "flag", "hg/hgc/hgc.c:9386", default="FALSE",
role="gate", verified=True, ticket="37893",
note="Use the modern single-page base-by-base alignment view (the one "
"hgBlat's new results page uses) for ordinary alignment clicks too "
"- mRNA/EST, PSL and similar hgc details - instead of the classic "
"two-frame