c2f76c5303384cddb6b48852c54c30437ee029f2 braney Sat Aug 8 08:49:33 2026 -0700 quickLift: order the target by rank in the source list, refs #38032 cdb3401bc60 carried each track's source priority into its hub stanza, on the assumption that the source group survives the lift and orders the target the same way it orders the source. It does not. trackHubAddGroupName rewrites the group of every hub track to the hub's own name, so all lifted tracks land in one QuickLift group and priorities from different source groups end up compared against each other. MANE is priority 100 in the genes group; wgEncodeRegDnaseWig is 1.8 in regulation. The group puts MANE on top on hg38; the raw priority put it at the bottom on the target. Write the track's rank in the list the caller has already sorted on group priority and then track priority. The rank counts every track walked past, not only the ones written out, so it depends only on how the source is laid out and does not shift with the append-per-request behaviour this ticket is about. A superTrack parent is not in that list and has no rank of its own, so it takes the rank of the first child that pulled it in, less a half. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> diff --git src/hg/lib/trackHub.c src/hg/lib/trackHub.c index 7f058529ffa..550425a9a12 100644 --- src/hg/lib/trackHub.c +++ src/hg/lib/trackHub.c @@ -1970,117 +1970,128 @@ // make sure we only output track types that can // be quickLifted. Return true if we any tracks survive { if (tdb->subtracks) { tdb->subtracks = validateTdbChildren(cart, db, tdb->subtracks, badList); if (tdb->subtracks == NULL) return FALSE; return TRUE; } return validateOneTdb(db, tdb, badList); } -static void outTrack(FILE *f, struct cart *cart, struct trackDb *tdb) +static void outTrack(FILE *f, struct cart *cart, struct trackDb *tdb, double priority) /* Set priority and output track to hub. */ { char buffer[1024]; -// Carry the source priority across rather than handing out a counter in walk order. -// The hub file is appended to across requests, so a counter ordered the target by the -// request a track happened to be added in instead of by how the source was laid out. -safef(buffer, sizeof buffer, "%g", tdb->priority); +safef(buffer, sizeof buffer, "%g", priority); hashReplace(tdb->settingsHash, "priority", cloneString(buffer)); struct dyString *dy = trackDbString(cart, tdb); fprintf(f, "%s\n", dy->string); } static boolean checkCartVisibility(struct cart *cart, struct trackDb *tdb) { char *cartVis = cartOptionalString(cart, tdb->track); if (cartVis != NULL) tdb->visibility = hTvFromString(cartVis); return (tdb->visibility != tvHide); } static boolean isFromQuickLiftHub(struct trackDb *tdb) /* True if this tdb came from a quickLift hub (already a lifted shadow track). * Such tracks must not be lifted again. */ { return trackDbSetting(tdb, "quickLiftUrl") != NULL || trackDbSetting(tdb, "quickLifted") != NULL; } static void walkTree(FILE *f, char *db, struct cart *cart, struct trackDb *tdb, struct dyString *visDy, struct trackDb **badList, struct hash *existingTracks) /* walk tree looking for visible tracks to output to hub. Skip tracks that already * came from a quickLift hub, and skip tracks whose name is already present in * the existing hub file. */ { struct hash *haveSuper = newHash(0); struct trackDb *tdbNext = NULL; +// The priority written to the hub is the track's rank in the source list, which the +// caller has sorted on group priority and then track priority. The rank has to count +// every track we walk past, not just the ones we output: the hub file is appended to +// across requests, so the number a track gets must depend only on how the source is +// laid out, never on which request it happened to be added in. +// +// The source priority itself will not do. All lifted tracks land in one group on the +// target (trackHubAddGroupName rewrites the group of every hub track), so a priority +// that only orders within a source group is being compared across groups. +double rank = 0; + for(; tdb; tdb = tdbNext) { tdbNext = tdb->next; + rank += 1; if (isFromQuickLiftHub(tdb)) continue; if (existingTracks != NULL && hashLookup(existingTracks, trackHubSkipHubName(tdb->track)) != NULL) continue; boolean isVisible = FALSE; if (tdb->parent == NULL) isVisible = checkCartVisibility(cart, tdb); else if (isParentVisible(cart, tdb) && isSubtrackVisible(cart, tdb)) // child of supertrack { if (hashLookup(haveSuper, tdb->parent->track) == NULL) // output yet? { //if (checkCartVisibility(cart, tdb->parent)) { char *bareParent = trackHubSkipHubName(tdb->parent->track); if (existingTracks == NULL || hashLookup(existingTracks, bareParent) == NULL) { tdb->parent->visibility = hTvFromString("tvShow"); - outTrack(f, cart, tdb->parent); + // a superTrack is not in the list we are walking, so it has no rank + // of its own. Slot it just above the first child that brought it in. + outTrack(f, cart, tdb->parent, rank - 0.5); } hashStore(haveSuper, tdb->parent->track); } } isVisible = checkCartVisibility(cart, tdb); } if (isVisible && validateTdb(cart, db, tdb, badList)) { hashRemove(tdb->settingsHash, "superTrack"); // this gets inherited by subTracks(?) // is this a custom track? char *tdbType = trackDbSetting(tdb, "tdbType"); if (tdbType != NULL) { hashReplace(tdb->settingsHash, "type", tdbType); hashReplace(tdb->settingsHash, "shortLabel", trackDbSetting(tdb, "name")); hashReplace(tdb->settingsHash, "longLabel", trackDbSetting(tdb, "description")); } - outTrack(f, cart, tdb); + outTrack(f, cart, tdb, rank); } } } static void readExistingHubTracks(char *filename, struct hash *trackNames) /* Scan an existing quickLift hub file and populate trackNames with the set of * track names already present. */ { struct lineFile *lf = lineFileMayOpen(filename, TRUE); if (lf != NULL) { char *line; while (lineFileNextReal(lf, &line)) { if (startsWithWord("track", line))