c683ecb63d721deb02fa8ab15bf66f70f1c3a326
max
  Sat Jul 25 18:25:00 2026 -0700
hgBlat/hgc: single-page BLAT results view with shareable alignment links

Add a modern single-page BLAT results table (hgBlat.js) and a non-frameset
alignment view (showSomeAlignmentModern in hgc, gated by the blatNewPage cart
var). Share/reopen a result set from a durable bigPsl custom track pinned in the
cart via a saved session (htcBlatAlign / loadBlatShareSessionIfAny). Factor the
shared helpers into a new blatShare module (lib/blatShare.c, inc/blatShare.h).

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index bdc8a98ff62..f4011c6fa18 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -237,30 +237,31 @@
 #include "chromInfo.h"
 #include "gbWarn.h"
 #include "mammalPsg.h"
 #include "net.h"
 #include "jsHelper.h"
 #include "virusClick.h"
 #include "gwasCatalog.h"
 #include "mdb.h"
 #include "yaleGencodeAssoc.h"
 #include "itemDetailsHtml.h"
 #include "trackVersion.h"
 #include "numtsClick.h"
 #include "geneReviewsClick.h"
 #include "bigBed.h"
 #include "bigPsl.h"
+#include "blatShare.h"
 #include "bedTabix.h"
 #include "longRange.h"
 #include "hmmstats.h"
 #include "aveStats.h"
 #include "trix.h"
 #include "bPlusTree.h"
 #include "customFactory.h"
 #include "dupTrack.h"
 #include "iupac.h"
 #include "clinvarSubLolly.h"
 #include "jsHelper.h"
 #include "errCatch.h"
 #include "htslib/bgzf.h"
 #include "htslib/kstring.h"
 #include "pipeline.h"
@@ -8219,30 +8220,31 @@
 if ((psl->strand[1] == '+') && (qType == gftProt))
     tEnd = psl->tStarts[psl->blockCount - 1] + psl->blockSizes[psl->blockCount - 1] * 3;
 
 tSeq = hDnaFromSeq(database, seqName, tStart, tEnd, dnaLower);
 
 freez(&tSeq->name);
 tSeq->name = cloneString(psl->tName);
 safef(tName, sizeof(tName), "%s.%s", organism, psl->tName);
 if (qName == NULL)
     fprintf(f, "<H2>Alignment of %s and %s:%d-%d</H2>\n",
 	    psl->qName, psl->tName, psl->tStart+1, psl->tEnd);
 else
     fprintf(f, "<H2>Alignment of %s and %s:%d-%d</H2>\n",
 	    qName, psl->tName, psl->tStart+1, psl->tEnd);
 
+if (!cartUsualBoolean(cart, "blatNewPage", FALSE))  /* no "frame" in the new single-page view */
     fputs("Click on links in the frame to the left to navigate through "
       "the alignment.\n", f);
 blockCount = pslShowAlignment(psl, qType == gftProt,
                               qName, qSeq, qStart, qEnd,
                               tName, tSeq, tStart, tEnd, f);
 freeDnaSeq(&tSeq);
 return blockCount;
 }
 
 static struct ffAli *pslToFfAliAndSequence(struct psl *psl, struct dnaSeq *qSeq,
 				    boolean *retIsRc, struct dnaSeq **retSeq,
 				    int *retTStart)
 /* Given psl, dig up target sequence and convert to ffAli.
  * Note: if strand is -, this does a pslRc to psl! */
 {
@@ -8334,30 +8336,31 @@
 
 if (restrictToWindow)
     {
     /* Find start/end in ffAli, which is clipped to target from PSL and maybe RC'd */
     ffStartEndQ(rna, ffAli, &rnaStart, &rnaEnd);
 
     /* Add 100 bases either side if possible */
     if (rnaStart >= 100) rnaStart -= 100;
     if (rnaEnd + 100 <= rnaSize) rnaEnd += 100;
     }
 
 /* Write body heading info. */
 char *displayChromName = chromAliasGetDisplayChrom(database, cart, psl->tName);
 fprintf(body, "<H2>Alignment of %s and %s:%d-%d</H2>\n",
 	psl->qName, displayChromName, partTStart+1, partTEnd);
+if (!cartUsualBoolean(cart, "blatNewPage", FALSE))  /* no "frame" in the new single-page view */
     fprintf(body, "Click on links in the frame to the left to navigate through "
 	"the alignment.\n");
 
 blockCount = ffShAliPart(body, ffAli, wholePsl->qName,
                          rna + rnaStart, rnaEnd - rnaStart, rnaStart,
 			 displayChromName, dnaSeq->dna, dnaSeq->size,
 			 wholeTStart, 8, FALSE, isRc,
 			 FALSE, TRUE, TRUE, TRUE, TRUE,
                          cdsS, cdsE, partTStart, partTEnd);
 return blockCount;
 }
 
 void showSomeAlignment(struct psl *psl, bioSeq *oSeq,
                        enum gfType qType, int qStart, int qEnd,
                        char *qName, int cdsS, int cdsE)
@@ -8996,66 +8999,277 @@
     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 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(). */
+{
+if (qName == NULL)
+    qName = psl->qName;
+char *chrom = chromAliasGetDisplayChrom(database, cart, psl->tName);
+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. */
+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}"
+       "#blatAlnBody{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color:#374a5e;"
+       " display:grid; grid-template-columns:220px 1fr}"      /* full-height sidebar + content column */
+       "#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 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 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}"
+       "</style>\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='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 (qType == gftRna || qType == gftDna)
+    showPartialDnaAlignment(psl, oSeq, stdout, cdsS, cdsE, FALSE);
+else
+    showGfAlignment(psl, oSeq, stdout, qType, qStart, qEnd, qName);
+printf("</div>\n");   /* #blatAlnContent */
+
+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. */
+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));
+
+/* "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. */
+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"
+    "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"
+    "  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"
+    "    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"
+    "      '&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"
+    "});\n"
+    "})();\n",
+    cartSessionId(cart), database, psl->tName, psl->tStart, psl->tStart, psl->tEnd, 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
+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);
-showSomeAlignment(psl, oSeq, qt, 0, oSeq->size, NULL, 0, 0);
+if (modern)
+    showSomeAlignmentModern(psl, oSeq, qt, 0, oSeq->size, NULL, 0, 0);  // cartWebStart page; framework closes it
+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 *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;
+    }
+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;
+    }
+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
 }
 
 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];
@@ -27126,61 +27340,98 @@
 boolean isProt = cgiOptionalString("isProt") != NULL;
 char *customTextTemplate = "track type=bigPsl indelDoubleInsert=on indelQueryInsert=on pslFile=%s visibility=pack showAll=on htmlUrl=http://%s/goldenPath/help/hgUserPsl.html %s bigDataUrl=%s name=\"%s\" description=\"%s\" colorByStrand=\"0,0,0 0,0,150\" mouseOver=\"${oChromStart}-${oChromEnd} of ${oChromSize} bp, strand ${oStrand}\"\n";  
 char *extraForMismatch = "indelPolyA=on showDiffBasesAllScales=. baseColorUseSequence=lfExtra baseColorDefault=diffBases";
   
 if (isProt)
     extraForMismatch = "";
 char buffer[4096];
 safef(buffer, sizeof buffer, customTextTemplate, bigBedTn.forCgi, host, extraForMismatch, bigBedTn.forCgi, trackName, trackDescription);
 
 struct customTrack *ctList = getCtList();
 struct customTrack *newCts = customFactoryParse(database, buffer, FALSE, NULL, NULL);
 theCtList = customTrackAddToList(ctList, newCts, NULL, FALSE);
 
 customTracksSaveCart(database, cart, theCtList);
 
+/* Pin this bigPsl file in the cart so hgBlat's Table view can reopen exactly these results from a
+ * shared session (see doShareReopen in hgBlat.c) - unambiguously, even when the cart holds several
+ * BLAT custom tracks from earlier searches. */
+cartSetString(cart, "blatLastBigBed", bigBedFile);
+
 cartSetString(cart, "i", "PrintAllSequences");
 hgCustom(newCts->tdb->track, NULL);
 
 if (sameOk(cartOptionalString(cart, "autoRearr"), "1"))  
     {
     char snakeVar[256];
     safef(snakeVar, sizeof snakeVar, "%s.doSnake", newCts->tdb->track);
     cartSetString(cart, snakeVar, "1");
     }
 
 }
 
 void doHPRCTable(struct trackDb *tdb, char *itemName)
 /* Put up a generic bigBed details page, with a table of links to turn on related
  *  * chain tracks with visibility toggles */
 {
 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. */
+{
+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"};
+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)
+        cartSetString(cart, keep[i], v);
+    }
+hashFree(&saved);
+}
+
 void doMiddle()
 /* Generate body of HTML. */
 {
+loadBlatShareSessionIfAny();
 char *track = cartString(cart, "g");
 char *item = cloneString(cartOptionalString(cart, "i"));
 char *parentWigMaf = cartOptionalString(cart, "parentWigMaf");
 struct trackDb *tdb = NULL;
 
 char *dupWholeName = NULL;
 boolean isDup = isDupTrack(track);
 if (isDup)
     {
     dupWholeName = track;
     track = dupTrackSkipToSourceName(track);
     }
 
 if (issueBotWarning)
     {
@@ -28032,30 +28283,34 @@
     {
     htcBigPslAliInWindow(item);
     }
 else if (sameWord(table, "htcBigPslAli"))
     {
     htcBigPslAli(item);
     }
 else if (sameWord(table, "htcCdnaAli"))
     {
     htcCdnaAli(item);
     }
 else if (sameWord(table, "htcUserAli"))
     {
     htcUserAli(item);
     }
+else if (sameWord(table, "htcBlatAlign"))
+    {
+    htcBlatAlign(item);
+    }
 else if (sameWord(table, "htcGetBlastPep"))
     {
     doGetBlastPep(item, cartString(cart, "aliTable"));
     }
 else if (sameWord(table, "htcProteinAli"))
     {
     htcProteinAli(item, cartString(cart, "aliTable"));
     }
 else if (sameWord(table, "htcBlatXeno"))
     {
     htcBlatXeno(item, cartString(cart, "aliTable"));
     }
 else if (sameWord(table, "htcExtSeq"))
     {
     htcExtSeq(item);