eb7e6ccb9b23c2dd5cd82de0237c6f25aecd2da1
max
  Thu Sep 17 06:02:11 2026 -0700
Hub tracks must not claim an undecorated name that a native track owns

#Preview2 week - bugs introduced now will need a build patch to fix
A hub track can be named on a URL, or found in the cart, without its "hub_<id>_"
prefix, so that hub links stay readable.  Nothing checked whether the assembly
already had a track of that name, so when it did, the hub track took the
variable: with the MethBase2 hub attached, setting the native hg38 Methbase
track's visibility moved it to hub_<id>_Methbase and dropped it from the native
track, which then did not draw.  The two share the name "Methbase", and their
subtracks share names too, so the same went for the _sel checkboxes.

hubTrackOwnsBareName() now gates the seven places that fall back to the bare
name - track and subtrack visibility, supertrack visibility, the two _hideKids
variables, and the _sel checkbox.  The bare name is the hub track's only where
the assembly has no track of that name; otherwise it belongs to the native one,
whose own pass over the track list consumes it.

It answers from the track list loadFromTrackDb() has already built, so it costs
a hash lookup: hub tracks are always prefixed in that list, so an undecorated
name can only match a native track.  A caller with no list to register (hgTrackUi)
falls back to a single-row trackDb query per distinct bare name, cached.  With
500 hub subtracks visible and 500 bare names on the URL - far past anything real
- that is 189 queries either way, against 1189 for a query per name.

Also clarifies the comments on hTrackDbWithCartVersion() and tdbForTrack(): the
"this result is cached" note means the shared-memory trackDb cache only, which
is off unless cacheTrackDbDir is set, and there is no memoizing besides it - so
hTrackDbForTrack() per name reloads the whole trackDb each call.  Measured at 57
seconds for 500 names before I abandoned that approach.

refs #34246

diff --git src/hg/lib/hdb.c src/hg/lib/hdb.c
index 02b1954b3f5..2d223bf244a 100644
--- src/hg/lib/hdb.c
+++ src/hg/lib/hdb.c
@@ -4401,31 +4401,36 @@
 struct trackDb *trackDbPolishAfterLinkup(struct trackDb *tdbList, char *db)
 /* Do various massaging that can only be done after parent/child
  * relationships are established. */
 {
 tdbList = pruneEmpties(tdbList, db, hIsPrivateHost() || hIsPreviewHost(), 0);
 return trackDbPolishAfterLinkupKeepAll(tdbList);
 }
 
 struct trackDb *hTrackDbWithCartVersion(char *db, int *retCartVersion)
 /* Load tracks associated with current db.
  * Supertracks are loaded as a trackDb, but are not in the returned list,
  * but are accessible via the parent pointers of the member tracks.  Also,
  * the supertrack trackDb subtrack fields are not set here (would be
  * incompatible with the returned list)
  * Returns list sorted by priority
- *	NOTE: this result is cached, do not free it !
+ *	NOTE: do not free this result - when the cache is on it is shared memory.
+ * NOTE: "cached" here means the shared-memory trackDb cache, which is only on when
+ * cacheTrackDbDir is set in hg.conf (it is not on a sandbox by default).  There is no
+ * memoizing besides that: with the cache off, every call reloads and relinks the whole
+ * trackDb, so calling this once per track name is quadratic.  Load the list once and
+ * pass it around - see tdbForTrack()'s tdbList argument.
  */
 {
 if (trackHubDatabase(db))
     return NULL;
 
 if (isHubTrack(db))
     {
     // this means the db has a hub_#_ prefix but didn't get loaded at init time
     unsigned hubId = hubIdFromTrackName(db);
     struct hubConnectStatus *status = hubFromIdNoAbort(hubId);
 
     if (status == NULL)
         errAbort("Error: The database in the hub (%s) that is saved in your session is no longer accessible."
                  " This may be due because the hub ID (%d) in your session cannot be transferred across"
                  " different Genome Browser machines.\n",hubConnectSkipHubPrefix(db), hubId);