bc236ffceb9ddc8632c872168c92c229206989af
max
Mon Aug 10 08:25:30 2026 -0700
hgc BLAT alignment viewer: consistent headings, section reorder, friendlier expired-link message, refs #37893
- Sidebar links and section headings are now all sentence case, and each
sidebar link matches its heading (the Side-by-side heading is relabeled too).
- Reorder the modern single-page view to Query, Side-by-side, Genome so the
per-block jump links sit under Only genome sequence, where their anchors
actually are, rather than under Side-by-side; tighten the block-link spacing.
The reorder is done server-side (capture the shared library output via
open_memstream, emit the sections in the new order) so the page does not
reflow after it loads.
- htcUserAli now shows a friendly 'no longer available, run a new BLAT search'
message when the search's trash files have aged out, instead of a raw
file-open error.
- Free the open_memstream buffer with libc free(), not kent freeMem().
diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 1f822180985..1e224043630 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -9092,31 +9092,31 @@
".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 */
" background:#fff}" /* edge to edge: no margin, no border */
"#blatAlnBody a{color:#0a3a7a}"
"#blatAlnBody a:hover{color:#8b1a1a}"
"#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{display:flex; flex-direction:column; gap:2px; margin:2px 0 0 14px}" /* block links, tight list indented under genome sequence */
"#blatAlnBlocks a{font-weight:400; font-size:13px}"
"#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:#4c759c; 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}"
/* undo bootstrap.css (pulled in by webStartGbNoBanner's gbHeader) on the sequence blocks:
* it would give
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}"
/* key-value summary strip, mirroring hgBlat's .blatStrip (label over value, thin dividers) */
".blatAlnStrip{display:flex; align-items:center; gap:24px; flex-wrap:wrap; margin:2px 0 14px}"
".blatAlnStat{display:flex; flex-direction:column; gap:1px}"
@@ -9130,99 +9130,122 @@
printf("\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 */
printf("\n");
printf("
\n");
-printf("
Alignment Summary
\n");
+printf("
Alignment summary
\n");
/* comma-format the coordinates and base counts, matching the new Table view (readable at the
* hundreds-of-millions scale of genomic coordinates, and the convention elsewhere in the browser) */
char tStartC[32], tEndC[32], matchC[32], qSizeC[32];
sprintLongWithCommas(tStartC, psl->tStart + 1);
sprintLongWithCommas(tEndC, psl->tEnd);
sprintLongWithCommas(matchC, psl->match + psl->repMatch);
sprintLongWithCommas(qSizeC, psl->qSize);
/* key-value strip (Query / Position / Identity / Matches / Strand), styled like hgBlat's summary
* strip so the two pages read as one design. */
printf("
"
"
Query%s
"
"
"
"
Position%s:%s-%s
"
"
"
"
Identity"
"%.1f%%
"
"
"
"
Matches%s of %s
"
"
"
"
Strand%s
"
"
\n",
qName, chrom, tStartC, tEndC, idColor, ident, matchC, qSizeC, psl->strand);
if (isNotEmpty(aliasStr))
printf("
Genome sequence %s is also known as: %s.
\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;
+/* Capture the shared library's alignment HTML so we can reorder its sections for this page. The
+ * library emits them as Query (#cDNA), Genome (#genomic), then Side-by-side (#ali), with the
+ * per-block anchors living inside the Genome section. We want Query, Side-by-side, Genome so the
+ * long per-block list sits at the bottom of both the page and the sidebar. Reorder here, on the
+ * server, rather than in JS, so the page does not reflow after it loads. */
+char *alnHtml = NULL;
+size_t alnLen = 0;
+FILE *alnF = open_memstream(&alnHtml, &alnLen);
if (qType == gftRna || qType == gftDna)
- blockCount = showPartialDnaAlignment(psl, oSeq, stdout, cdsS, cdsE, FALSE);
+ blockCount = showPartialDnaAlignment(psl, oSeq, alnF, cdsS, cdsE, FALSE);
else
- blockCount = showGfAlignment(psl, oSeq, stdout, qType, qStart, qEnd, qName);
+ blockCount = showGfAlignment(psl, oSeq, alnF, qType, qStart, qEnd, qName);
+fclose(alnF);
+char *pGenome = (alnHtml != NULL) ? stringIn("
\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("
\n");
printf("
\n"); /* #blatAlnBody */
-/* The cDNA/Genomic section headers come from shared library code (fuzzyShow.c / pslShow.c) as
- * "cDNA " / "Genomic :"; 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. */
+/* The cDNA/Genomic/Side-by-side section headers come from shared library code (fuzzyShow.c /
+ * pslShow.c) as "cDNA " / "Genomic :" / "Side by Side Alignment"; relabel them to
+ * the sidebar wording (sentence case) via JS (there is no C hook for it), keeping the
+ * #cDNA/#genomic/#ali 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"
+ " var star = /\\*\\s*$/.test(h.textContent) ? '*' : '';\n" // keep the footnote marker if present
" h.textContent = '';\n"
" var k = document.createElement('a'); k.name = anchor; h.appendChild(k);\n"
- " h.appendChild(document.createTextNode(text));\n"
+ " h.appendChild(document.createTextNode(text + star));\n"
" }\n"
"}\n"
"relabel('cDNA', 'Only query sequence: %s');\n"
"relabel('genomic', 'Only genome sequence: %s');\n"
+ "relabel('ali', 'Side by side alignment');\n" // match the sidebar wording and sentence case
"})();\n",
qName, chrom);
/* "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 btn = document.getElementById('blatShareBtn');\n"
"if (!btn) return;\n"
"btn.addEventListener('click', function(ev){\n"
" ev.preventDefault();\n"
" if (btn.dataset.busy) return;\n"
@@ -9260,30 +9283,37 @@
boolean modern = cartUsualBoolean(cart, "blatNewPage", FALSE);
char title[1024];
safef(title, sizeof title, "User Sequence vs Genomic");
if (modern)
{
char pageTitle[256];
safef(pageTitle, sizeof pageTitle, "BLAT Base Alignment: %s", blatAsmLabel(database));
webStartGbNoBanner(cart, database, pageTitle); // menubar + , no legacy section tables
}
else
htmlFramesetStart(title);
start = cartInt(cart, "o");
parseSs(fileNames, &pslName, &faName, &qName);
+if (modern && (!fileExists(pslName) || !fileExists(faName)))
+ { /* the search's trash files have been cleaned up: a friendly note, not a raw file error */
+ printf("This BLAT alignment is no longer available. The search results it came from have "
+ "expired. Please run a new BLAT search.
\n");
+ webEndGb();
+ exit(0);
+ }
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))