6ae582f5103a8b27420c44a8f92f210969f65f4c
max
  Sat Jul 25 21:36:18 2026 -0700
hgc BLAT alignment viewer: table-free page, share dialog, chromAlias, block-count fix

refs #37893

Modern base-alignment page (showSomeAlignmentModern):
- Switch page chrome to webStartGbNoBanner()/webEndGb() (menubar + <main>, no legacy
nested section tables) and draw the gold title bar + grey Back/Share buttons in plain
HTML instead of injecting them with JS.
- Full-height "jump to" sidebar; its links are position:sticky so they stay visible while
scrolling the long alignment. The per-block "Block N" links use the block count the
renderer returns, not psl->blockCount. The DNA path (ffShAliPart) merges blocks
separated by gaps <= 8 bases, so raw psl->blockCount over-counted and produced links to
#N anchors that did not exist; the returned count matches the emitted anchors.
- Show the genomic sequence's chromAlias names ("Genome sequence chr7 is also known as:
..."), de-duplicated. Friendly assembly label (organism + accession) for hub databases
instead of the internal hub_NNN_ name.

Durable "Share a link":
- Opens the shared topLinks.js "Share a link" modal and hands it a durable
hgc?g=htcBlatAlign link. The link carries only the hit selectors (c/o/i) plus u=l&s=;
db and the browser window come from the saved session. htcBlatAlign rebuilds the one
alignment from the session's durable bigPsl custom track (no BLAT re-run).
- Restore the per-hit "Alignment" links on shared-session results tables (hgBlat.c).

lib/cart.c:
- cartWriteHeaderAndCont() is now idempotent (guards on cartDidContentType). hgc writes
the CGI header early, so a later webStartGb* would otherwise write a second
Set-Cookie/Content-Type into the page body.

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index f4011c6fa18..1176e8aafed 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -8999,277 +8999,320 @@
     safef(name, sizeof name, "%s", psl->qName);
     }
 else
     {
     qSeq = loadGenomePart(otherDb, psl->qName, psl->qStart, psl->qEnd);
     safef(name, sizeof name, "%s.%s", otherOrg, psl->qName);
     }
 char title[1024];
 safef(title, sizeof title, "%s %s vs %s %s ",
        (otherOrg == NULL ? "" : otherOrg), psl->qName, org, psl->tName );
 htmlFramesetStart(title);
 /*showSomeAlignment(psl, qSeq, gftDnaX, psl->qStart, psl->qEnd, name, 0, 0); */
 showSomeAlignment(psl, qSeq, gftDnaX, psl->qStart, psl->qEnd, name, cdsStart, cdsEnd);
 }
 
+static char *blatAsmLabel(char *database)
+/* A user-facing assembly label for the page title.  For an assembly hub the internal
+ * "hub_NNN_GCA_..." database name is not helpful, so use the assembly's friendly organism plus its
+ * accession; for a native assembly just use the db name (e.g. "hg38"). */
+{
+if (!trackHubDatabase(database))
+    return cloneString(database);
+char *acc = trackHubSkipHubName(database);   /* drop the "hub_NNN_" prefix -> the accession */
+char *org = hGenome(acc);                    /* GenArk table's friendly genome name for GC* accs */
+if (isEmpty(org))
+    org = trackHubAssemblyField(database, "organism");   /* else the hub's genomes.txt organism */
+if (isEmpty(org))
+    return cloneString(acc);
+char buf[256];
+safef(buf, sizeof buf, "%s %s", org, acc);
+return cloneString(buf);
+}
+
 static void showSomeAlignmentModern(struct psl *psl, bioSeq *oSeq, enum gfType qType,
                        int qStart, int qEnd, char *qName, int cdsS, int cdsE)
-/* Modern single-page version of showSomeAlignment for hgBlat's new table mode: a one-line summary,
- * a back link and three "jump to" links, then the base-by-base alignment inlined below with
- * steel-blue section headers - all in one white panel, so the whole page scrolls (no <frameset>).
- * The alignment body itself is generated exactly as before.  The caller supplies the page chrome
- * via cartWebStart(). */
+/* Modern single-page version of showSomeAlignment for hgBlat's new table mode: a gold title bar with
+ * Back/Share buttons, a full-height "jump to" sidebar, then an "Alignment Summary" and the base-by-
+ * base alignment inlined below with steel-blue section headers, so the whole page scrolls (no
+ * <frameset>).  The alignment body itself is generated by the shared library as before.  The caller
+ * supplies the page chrome via webStartGbNoBanner()/webEndGb() - a menubar and <main> with no legacy
+ * section tables - so everything here is plain, table-free HTML. */
 {
 if (qName == NULL)
     qName = psl->qName;
 char *chrom = chromAliasGetDisplayChrom(database, cart, psl->tName);
+/* Alternate (chromAlias) names for the genomic sequence - e.g. its RefSeq/GenBank/Ensembl accessions
+ * - shown after the main name in the "Only genome sequence" header. */
+struct dyString *aliasDy = dyStringNew(128);
+struct slName *aliasList = chromAliasFindAliases(psl->tName), *al;
+struct hash *seenAlias = hashNew(0);
+hashStore(seenAlias, chrom);        /* skip the name already shown, and the native name */
+hashStore(seenAlias, psl->tName);
+boolean firstAlias = TRUE;
+for (al = aliasList; al != NULL; al = al->next)
+    {
+    if (isEmpty(al->name) || hashLookup(seenAlias, al->name))
+        continue;   /* skip empties, the shown name, and duplicates (e.g. Ensembl and GenBank "7") */
+    hashStore(seenAlias, al->name);
+    dyStringPrintf(aliasDy, "%s%s", firstAlias ? "" : ", ", al->name);
+    firstAlias = FALSE;
+    }
+hashFree(&seenAlias);
+char *aliasStr = dyStringCannibalize(&aliasDy);   /* "NC_000007.14, CM000669.2, 7" or "" */
 double ident = 100.0 - pslCalcMilliBad(psl, TRUE) * 0.1;
 
 char *idColor = (ident >= 98) ? "#1f7a34" : (ident >= 95) ? "#4d7c0f" :
                 (ident >= 90) ? "#b45309" : "#b1301f";
 
 /* Offer "Share a link" only when a durable bigPsl custom track backs these results; without it there
  * is nothing for a shared session to rebuild the alignment from. */
 char *shareBb = blatFindPinnedBigPsl(cart);
 boolean canShare = (shareBb != NULL);
 freeMem(shareBb);
 
-/* Colors imported from the BLAT Redesign (slide 3): grey page, steel-blue section-header bars,
- * navy links with maroon hover, slate text.  The <h2>/<hr> the shared alignment code emits are
- * hidden; its <h4> section headings become the steel-blue bars. */
+/* Colors imported from the BLAT Redesign (slide 3): grey page, steel-blue section-header bars, navy
+ * links with maroon hover, slate text.  The <h2>/<hr> the shared alignment code emits are hidden; its
+ * <h4> section headings become the steel-blue bars.  The page chrome is webStartGbNoBanner (a menubar
+ * and <main>, no legacy section tables), so we draw our own gold title bar in plain HTML. */
 printf("<style>"
-       "#main-menu-whole{margin-bottom:0}"   /* drop the 5px gap (cream body) between nav and title */
-       ".subheadingBar{background:#e9cf9a; color:#0a2b6b; box-sizing:border-box; display:flex;"
-       " align-items:center; justify-content:space-between; padding:5px 14px}"  /* gold title band */
-       "table.hgInside{background:#eef1f4}"
+       "#main-menu-whole{margin-bottom:0}"   /* no gap between the menubar and the title bar */
+       "#mainContent{background:#eef1f4}"     /* grey page behind the white alignment panel */
+       ".blatTitleBar{background:#e9cf9a; color:#0a2b6b; box-sizing:border-box; display:flex;"
+       " align-items:center; justify-content:space-between; padding:8px 16px}"  /* gold title band */
+       ".blatTitleBar .blatTtl{font-size:18px; font-weight:700}"
+       ".blatTitleBar .blatBtns{display:flex; gap:8px; align-items:center}"
+       ".blatBtn{padding:4px 12px; font-size:13px; border:1px solid #999; border-radius:3px;"
+       " background:#e6e6e6; text-decoration:none; white-space:nowrap; cursor:pointer}"
+       /* nice_menu.css sets a:link blue (specificity 0,1,1); a.blatBtn:link (0,2,1) beats it */
+       "a.blatBtn:link, a.blatBtn:visited, a.blatBtn:hover{color:#000; text-decoration:none}"
+       ".blatBtn:hover{background:#d8d8d8}"
        "#blatAlnBody{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color:#374a5e;"
-       " display:grid; grid-template-columns:220px 1fr}"      /* full-height sidebar + content column */
+       " display:grid; grid-template-columns:220px 1fr;"      /* full-height sidebar + content column */
+       " background:#fff}"                                    /* edge to edge: no margin, no border */
        "#blatAlnBody a{color:#0a3a7a}"
        "#blatAlnBody a:hover{color:#8b1a1a}"
-       "#blatAlnNav{background:#f4f7fb; border-right:1px solid #dde3ea; padding:18px 20px;"
-       " display:flex; flex-direction:column; gap:16px}"
+       "#blatAlnNav{grid-column:1; grid-row:1; background:#f4f7fb; border-right:1px solid #dde3ea}"
+       /* keep the grey column full height, but pin the links so they stay visible while scrolling */
+       "#blatAlnNavInner{position:sticky; top:0; padding:18px 20px; display:flex; flex-direction:column;"
+       " gap:16px}"
        "#blatAlnNav a{font-weight:700; text-decoration:none}"   /* already obviously links; no underline */
        "#blatAlnBlocks{display:flex; flex-direction:column; gap:8px; margin:2px 0 0 14px}"  /* block links, indented under side-by-side */
        "#blatAlnBlocks a{font-weight:400; font-size:13px}"
-       "#blatAlnContent{min-width:0; padding:0 20px 14px}"
+       "#blatAlnContent{grid-column:2; grid-row:1; min-width:0; padding:0 20px 14px}"
        "#blatAlnContent h2{display:none}"
        "#blatAlnContent hr{display:none}"
        "#blatAlnContent h4{margin:16px -20px 0; padding:8px 20px; background:#4c7093; color:#fff;"
        " font-size:15px; font-weight:700}"                     /* -20px: bar spans full content width */
        "#blatAlnContent h4:first-child{margin-top:0}"          /* Alignment Summary flush at top */
        "#blatAlnContent h4 a{color:#fff}"
-       "#blatAlnContent pre{margin:0; padding:2px 0 12px; line-height:1.4}"
+       /* undo bootstrap.css (pulled in by webStartGbNoBanner's gbHeader) on the sequence blocks:
+        * it would give <pre> a grey box, a border and word-break that mangles the alignment */
+       "#blatAlnContent pre{margin:0; padding:2px 0 12px; line-height:1.4; background:none; border:0;"
+       " border-radius:0; color:#374a5e; white-space:pre; word-break:normal; word-wrap:normal}"
        "</style>\n");
 
+/* gold title bar, drawn directly (no framework subheadingBar, no JS): title on the left, then a
+ * "Share a link" button (when a durable track backs the results) and a "Back to results" button. */
+printf("<div class='blatTitleBar'>");
+printf("<span class='blatTtl'>BLAT Base Alignment: %s</span>", blatAsmLabel(database));
+printf("<span class='blatBtns'>");
+printf("<a href='hgBlat?blatReopen=1&hgsid=%s' class='blatBtn'>"
+       "\xe2\x80\xb9 Back to results</a>", cartSessionId(cart));
+if (canShare)
+    printf("<a href='#' id='blatShareBtn' class='blatBtn'>Share a link</a>");
+printf("</span></div>\n");
+
 /* one white panel laid out as two grid columns: a full-height "jump to" sidebar on the left, and on
  * the right an "Alignment Summary" header, the summary line, and the base-by-base alignment inlined
  * so the whole page scrolls */
-if (canShare)
-    printf("<div id='blatAlnShareBox' style='display:none; margin:0 0 12px; padding:10px 14px; "
-           "background:#f4f7fb; border:1px solid #dde3ea; border-radius:3px; font-size:13px; "
-           "color:#374a5e'></div>\n");
-
-printf("<div id='blatAlnBody' style='background:#fff; border:1px solid #c4cdd6; "
-       "overflow:hidden; margin:0 0 20px'>\n");
-
-printf("<div id='blatAlnNav'>\n");
-printf("<a href='#cDNA'>Only query sequence</a>\n"
-       "<a href='#genomic'>Only genome sequence</a>\n"
-       "<a href='#ali'>Side-by-side alignment</a>\n");
-if (psl->blockCount > 1)   /* per-block jump links, indented under the side-by-side item */
-    {
-    int bi;
-    printf("<div id='blatAlnBlocks'>\n");
-    for (bi = 1;  bi <= psl->blockCount;  ++bi)
-        printf("<a href='#%d'>Block %d</a>\n", bi, bi);
-    printf("</div>\n");
-    }
-printf("</div>\n");
+printf("<div id='blatAlnBody'>\n");
 
 printf("<div id='blatAlnContent'>\n");
 printf("<h4>Alignment Summary</h4>\n");
 printf("<p><b>%s</b> aligned to <b>%s:%d-%d</b>, "
        "<b style='color:%s'>%.1f%% identity</b>, "
        "%d of %d bases matched, strand <b>%s</b>.</p>\n",
        qName, chrom, psl->tStart + 1, psl->tEnd, idColor, ident,
        psl->match + psl->repMatch, psl->qSize, psl->strand);
+if (isNotEmpty(aliasStr))
+    printf("<p>Genome sequence %s is also known as: %s.</p>\n", chrom, aliasStr);
+/* The shared library returns the number of alignment blocks it actually shows.  The DNA path merges
+ * blocks separated by gaps <= 8 bases, so this can be fewer than psl->blockCount; use it (not
+ * psl->blockCount) so the sidebar's "Block N" links match the #1..#N anchors that were emitted. */
+int blockCount;
 if (qType == gftRna || qType == gftDna)
-    showPartialDnaAlignment(psl, oSeq, stdout, cdsS, cdsE, FALSE);
+    blockCount = showPartialDnaAlignment(psl, oSeq, stdout, cdsS, cdsE, FALSE);
 else
-    showGfAlignment(psl, oSeq, stdout, qType, qStart, qEnd, qName);
+    blockCount = showGfAlignment(psl, oSeq, stdout, qType, qStart, qEnd, qName);
 printf("</div>\n");   /* #blatAlnContent */
 
+/* Sidebar, emitted after the alignment so blockCount is known; CSS grid puts it back in column 1.
+ * The inner div is position:sticky so the links stay in view as the long alignment scrolls. */
+printf("<div id='blatAlnNav'><div id='blatAlnNavInner'>\n");
+printf("<a href='#cDNA'>Only query sequence</a>\n"
+       "<a href='#genomic'>Only genome sequence</a>\n"
+       "<a href='#ali'>Side-by-side alignment</a>\n");
+if (blockCount > 1)   /* per-block jump links, indented under the side-by-side item */
+    {
+    int bi;
+    printf("<div id='blatAlnBlocks'>\n");
+    for (bi = 1;  bi <= blockCount;  ++bi)
+        printf("<a href='#%d'>Block %d</a>\n", bi, bi);
+    printf("</div>\n");
+    }
+printf("</div></div>\n");
+
 printf("</div>\n");   /* #blatAlnBody */
 
-/* Two touch-ups that need the rendered DOM: (1) the cDNA/Genomic section headers come from shared
- * library code (fuzzyShow.c / pslShow.c) as "cDNA <qName>" / "Genomic <chrom> :" - relabel them to
- * match the sidebar wording, keeping the #cDNA/#genomic jump anchors; (2) add a right-aligned "Back
- * to results" button into the gold title band.  qName and chrom are already sanitized. */
+/* The cDNA/Genomic section headers come from shared library code (fuzzyShow.c / pslShow.c) as
+ * "cDNA <qName>" / "Genomic <chrom> :"; relabel them to the sidebar wording via JS (there is no C
+ * hook for it), keeping the #cDNA/#genomic jump anchors.  qName and chrom are already sanitized. */
 jsInlineF(
     "(function(){\n"
     "function relabel(anchor, text){\n"
     "  var a = document.getElementsByName(anchor);\n"
     "  if (a && a.length){\n"
     "    var h = a[0].parentNode;\n"
     "    h.textContent = '';\n"
     "    var k = document.createElement('a'); k.name = anchor; h.appendChild(k);\n"
     "    h.appendChild(document.createTextNode(text));\n"
     "  }\n"
     "}\n"
     "relabel('cDNA', 'Only query sequence: %s');\n"
     "relabel('genomic', 'Only genome sequence: %s');\n"
-    "var t = document.getElementById('sectTtl');\n"
-    "if (t) t.textContent = 'BLAT Base Alignment: %s';\n"
-    "var bar = document.querySelector('.subheadingBar');\n"
-    "if (bar){\n"
-    "  var b = document.createElement('a');\n"
-    "  b.id = 'blatBackBtn';\n"
-    "  b.href = 'hgBlat?blatReopen=1&hgsid=%s';\n"
-    "  b.textContent = '\\u2039 Back to results';\n"
-    "  b.style.cssText = 'padding:5px 15px; background:#0a2b6b; color:#fff; font-weight:700;"
-    " font-size:13px; border-radius:3px; text-decoration:none; white-space:nowrap';\n"
-    "  bar.appendChild(b);\n"
-    "}\n"
     "})();\n",
-    qName, chrom, database, cartSessionId(cart));
+    qName, chrom);
 
-/* "Share a link" button in the gold title band: save an anonymous session (hgSession API), then
- * build a durable hgc?g=htcBlatAlign link that rebuilds THIS alignment from the session's durable
- * bigPsl custom track (no BLAT re-run, no stored trash sequence).  Mirrors hgBlat.js blatShareLink();
- * qName is already sanitized. */
+/* "Share a link": save an anonymous session (hgSession API), build a durable hgc?g=htcBlatAlign link
+ * that rebuilds THIS alignment from the session's durable bigPsl custom track (no BLAT re-run, no
+ * stored trash sequence), and hand it to the shared "Share a link" modal (topLinks.js shareUrl,
+ * loaded by the menu bar) so it looks like every other share dialog.  qName is already sanitized. */
 if (canShare)
     jsInlineF(
     "(function(){\n"
-    "var bar = document.querySelector('.subheadingBar');\n"
-    "if (!bar) return;\n"
-    "var btn = document.createElement('a');\n"
-    "btn.href = '#';\n"
-    "btn.textContent = 'Share a link';\n"
-    "btn.style.cssText = 'padding:5px 15px; background:#fff; color:#0a2b6b; border:1px solid #0a2b6b;"
-    " font-weight:700; font-size:13px; border-radius:3px; text-decoration:none; white-space:nowrap;"
-    " cursor:pointer';\n"
-    "var backBtn = document.getElementById('blatBackBtn');\n"
-    "if (backBtn) bar.insertBefore(btn, backBtn); else bar.appendChild(btn);\n"
-    "var box = document.getElementById('blatAlnShareBox');\n"
+    "var btn = document.getElementById('blatShareBtn');\n"
+    "if (!btn) return;\n"
     "btn.addEventListener('click', function(ev){\n"
     "  ev.preventDefault();\n"
-    "  if (!box) return;\n"
-    "  box.style.display = 'block';\n"
-    "  box.textContent = 'Creating shareable link\\u2026';\n"
+    "  if (btn.dataset.busy) return;\n"
+    "  btn.dataset.busy = '1';\n"
+    "  var label = btn.textContent;\n"
+    "  btn.textContent = 'Creating link\\u2026';\n"
     "  fetch('../cgi-bin/hgSession', {method:'POST', credentials:'same-origin',"
     " headers:{'Content-Type':'application/x-www-form-urlencoded'},"
     " body:'hgsid=%s&hgS_doSaveSessionJson=1&hgS_shareAnon=1'})\n"
     "  .then(function(r){ return r.json(); }).then(function(data){\n"
-    "    if (!data || !data.name){ box.textContent = 'Could not create link.'; return; }\n"
+    "    btn.textContent = label; btn.dataset.busy = '';\n"
+    "    if (!data || !data.name) return;\n"
     "    var link = window.location.origin + window.location.pathname +\n"
-    "      '?g=htcBlatAlign&db=%s&c=%s&o=%d&l=%d&r=%d&i=' + encodeURIComponent('%s') +\n"
+    "      '?g=htcBlatAlign&c=%s&o=%d&i=' + encodeURIComponent('%s') +\n"  // db comes from the session
     "      '&u=l&s=' + encodeURIComponent(data.name);\n"
-    "    box.innerHTML = '';\n"
-    "    var msg = document.createElement('div');\n"
-    "    msg.textContent = 'Shareable link (opens this alignment for anyone):';\n"
-    "    msg.style.marginBottom = '6px';\n"
-    "    var inp = document.createElement('input');\n"
-    "    inp.type = 'text'; inp.readOnly = true; inp.value = link;\n"
-    "    inp.style.cssText = 'width:70%%; max-width:640px; font-size:13px; padding:5px 8px;"
-    " border:1px solid #c4cdd6; border-radius:3px';\n"
-    "    var cp = document.createElement('button');\n"
-    "    cp.type = 'button'; cp.textContent = 'Copy';\n"
-    "    cp.style.cssText = 'margin-left:8px; padding:5px 12px; font-size:13px;"
-    " border:1px solid #0a2b6b; background:#0a2b6b; color:#fff; border-radius:3px; cursor:pointer';\n"
-    "    cp.addEventListener('click', function(){ inp.select();"
-    " if (navigator.clipboard){ navigator.clipboard.writeText(link); }"
-    " else { document.execCommand('copy'); } cp.textContent = 'Copied'; });\n"
-    "    box.appendChild(msg); box.appendChild(inp); box.appendChild(cp);\n"
-    "  }).catch(function(){ box.textContent = 'Could not reach the server. Please try again.'; });\n"
+    "    if (window.topLinks && topLinks.shareUrl) topLinks.shareUrl(link);\n"
+    "  }).catch(function(){ btn.textContent = label; btn.dataset.busy = ''; });\n"
     "});\n"
     "})();\n",
-    cartSessionId(cart), database, psl->tName, psl->tStart, psl->tStart, psl->tEnd, qName);
+    cartSessionId(cart), psl->tName, psl->tStart, qName);
 }
 
 void htcUserAli(char *fileNames)
 /* Show alignment for accession. */
 {
 char *pslName, *faName, *qName;
 struct lineFile *lf;
 bioSeq *oSeqList = NULL, *oSeq = NULL;
 struct psl *psl;
 int start;
 enum gfType tt, qt;
 boolean isProt;
 /* In hgBlat's new table mode (blatNewPage) show a modern single-page alignment instead of the
  * classic two-frame <frameset>. */
 boolean modern = cartUsualBoolean(cart, "blatNewPage", FALSE);
 
 char title[1024];
 safef(title, sizeof title, "User Sequence vs Genomic");
 if (modern)
-    cartWebStart(cart, database, "BLAT Base Alignment");   // full page chrome: menubar + sans-serif
+    {
+    char pageTitle[256];
+    safef(pageTitle, sizeof pageTitle, "BLAT Base Alignment: %s", blatAsmLabel(database));
+    webStartGbNoBanner(cart, database, pageTitle);   // menubar + <main>, no legacy section tables
+    }
 else
     htmlFramesetStart(title);
 
 start = cartInt(cart, "o");
 parseSs(fileNames, &pslName, &faName, &qName);
 pslxFileOpen(pslName, &qt, &tt, &lf);
 isProt = (qt == gftProt);
 while ((psl = pslNext(lf)) != NULL)
     {
     if (sameString(psl->tName, seqName) && psl->tStart == start && sameString(psl->qName, qName))
         break;
     pslFree(&psl);
     }
 lineFileClose(&lf);
 if (psl == NULL)
     errAbort("Couldn't find alignment at %s:%d", seqName, start);
 oSeqList = faReadAllSeq(faName, !isProt);
 for (oSeq = oSeqList; oSeq != NULL; oSeq = oSeq->next)
     {
     if (sameString(oSeq->name, qName))
 	break;
     }
 if (oSeq == NULL)  errAbort("%s is in %s but not in %s. Internal error.", qName, pslName, faName);
 if (modern)
-    showSomeAlignmentModern(psl, oSeq, qt, 0, oSeq->size, NULL, 0, 0);  // cartWebStart page; framework closes it
+    {
+    showSomeAlignmentModern(psl, oSeq, qt, 0, oSeq->size, NULL, 0, 0);
+    webEndGb();
+    exit(0);   // we drew the whole page; skip the framework's table-closing cartHtmlEnd
+    }
 else
     showSomeAlignment(psl, oSeq, qt, 0, oSeq->size, NULL, 0, 0);        // classic frameset; exits itself
 }
 
 void htcBlatAlign(char *qName)
 /* Durable base-by-base alignment for a shared BLAT link (g=htcBlatAlign): rebuild one alignment from
  * the saved session's durable bigPsl custom track (blatLastBigBed) instead of the ephemeral trash
  * .pslx/.fa the fresh-search htcUserAli path reads.  seqName and o identify the hit; the query
  * sequence comes from the bigPsl record itself, so no stored trash sequence is needed.  This backs
  * the "Share a link" button on the modern alignment page. */
 {
-cartWebStart(cart, database, "BLAT Base Alignment");   // full page chrome: menubar + sans-serif
+char pageTitle[256];
+safef(pageTitle, sizeof pageTitle, "BLAT Base Alignment: %s", database);
+webStartGbNoBanner(cart, database, pageTitle);   // menubar + <main>, no legacy section tables
 char *bbFile = blatFindPinnedBigPsl(cart);
 if (bbFile == NULL || !fileExists(bbFile))
     {
     printf("<p>This shared BLAT alignment is no longer available. The custom track that stored it "
            "has expired or been removed. Please run a new <a href=\"hgBlat\">BLAT search</a>.</p>\n");
-    return;
+    webEndGb();
+    exit(0);
     }
 int start = cartInt(cart, "o");
 char *seq = NULL;
 struct psl *psl = pslFromBigPslFileMatch(bbFile, seqName, start, qName, &seq, NULL);
 if (psl == NULL || seq == NULL)
     {
     printf("<p>This alignment was not found in the shared BLAT results.</p>\n");
-    return;
+    webEndGb();
+    exit(0);
     }
 enum gfType qType = pslIsProtein(psl) ? gftProt : gftDna;
 struct dnaSeq *oSeq = newDnaSeq(cloneString(seq), strlen(seq), qName);
-showSomeAlignmentModern(psl, oSeq, qType, 0, oSeq->size, NULL, 0, 0);  // cartWebStart page; framework closes it
+showSomeAlignmentModern(psl, oSeq, qType, 0, oSeq->size, NULL, 0, 0);
+webEndGb();
+exit(0);   // we drew the whole page; skip the framework's table-closing cartHtmlEnd
 }
 
 void htcProteinAli(char *readName, char *table)
 /* Show protein to translated dna alignment for accession. */
 {
 struct psl *psl;
 int start;
 enum gfType qt = gftProt;
 struct sqlResult *sr;
 struct sqlConnection *conn = hAllocConn(database);
 struct dnaSeq *seq = NULL;
 char query[256], **row;
 char fullTable[HDB_MAX_TABLE_STRING];
 boolean hasBin;
 char buffer[256];
@@ -27376,38 +27419,40 @@
 int start = cartInt(cart, "o");
 int end = cartInt(cart, "t");
 genericHeader(tdb, itemName);
 genericBigBedClick(NULL, tdb, itemName, start, end, 0);
 printTrackHtml(tdb);
 // tell the javscript to reorganize the column of assemblies:
 jsIncludeFile("hgc.js", NULL);
 jsInlineF("var doHPRCTable = true;\n");
 }
 
 boolean findNameBasedHandler(struct trackDb *tdb, char *track, char *item);
 
 static void loadBlatShareSessionIfAny()
 /* A durable BLAT "Share a link" alignment (hgc?g=htcBlatAlign&u=l&s=NAME&...) rebuilds one alignment
  * from a saved anonymous session's durable bigPsl custom track.  Load that session so the cart gets
- * its blatLastBigBed and custom track, then restore this link's own db/position/track/item, which
- * identify the specific alignment rather than the session's saved browser view. */
+ * its db, blatLastBigBed and custom track, then restore the link's own hit selectors, which identify
+ * which single alignment to show rather than the session's saved browser view.  Only g/c/o/i are
+ * selectors (the handler, chrom, start and query name); db and the browser window come from the
+ * session, so the shared link needs to carry only those four. */
 {
 if (cgiOptionalString("s") == NULL || !sameOk(cgiOptionalString("g"), "htcBlatAlign"))
     return;
-/* The whole-cart session load can overwrite these with the session's saved values; remember the
- * link's own copies and put them back afterwards. */
-char *keep[] = {"g", "db", "c", "o", "t", "l", "r", "i"};
+/* The whole-cart session load frees the cart's current values; remember the link's own selectors and
+ * put them back afterwards (g and i are also excluded from saved sessions, so they must come here). */
+char *keep[] = {"g", "c", "o", "i"};
 struct hash *saved = hashNew(0);
 int i;
 for (i = 0; i < ArraySize(keep); ++i)
     {
     char *v = cgiOptionalString(keep[i]);
     if (v != NULL)
         hashAdd(saved, keep[i], cloneString(v));
     }
 struct sqlConnection *sConn = hConnectCentral();
 cartLoadUserSession(sConn, cgiUsualString("u", "l"), cgiString("s"), cart, NULL, NULL);
 hDisconnectCentral(&sConn);
 for (i = 0; i < ArraySize(keep); ++i)
     {
     char *v = hashFindVal(saved, keep[i]);
     if (v != NULL)