5a249cd50f592a3b7598110eec792cfc942fab3f
max
  Wed Sep 9 08:52:06 2026 -0700
Address the v504 code review

hgSession: an anonymous share name that arrives with the request is saved only
when it is not already in the table.  Every anonymous link sits under the one
reserved user "l", so a name already there stays as it is and the caller is told
so.  The top-right Share dialog is unaffected, since it passes a name it has
just reserved and such a name does not exist yet.  Snapshot names are now left
out of both My Sessions listings, which is what their "__" prefix has claimed
all along.

Share dialog: "Create link & copy" reports the copy instead of promising it.
copyToClipboard says whether the text reached the clipboard, the dialog passes
that on when a browser refuses, and it tries the asynchronous clipboard API
before giving up.  The preview is built with the same encoding the server uses,
so a name holding a hyphen or a slash previews as the link that really gets
made.  Cancelling out of the name editor no longer copies a second time, and a
reply with no link in it says so rather than showing "undefined".

hgBlat: a second click on the share button while the first request is still out
no longer mints a second snapshot session, and a box dismissed during the wait
stays closed.

Also: a snapshot moves a cart value into durable storage only when it is a
trash path, the way sessionData's own callers check; sqlAddressMatch keeps to
its own documented precondition when handed an empty address; alphaGenomeToWig
compares its output with its input rather than with itself, rejects a position
that is not all digits and skips an empty score; and hgc's default iframe width
reaches the browser as one percent sign.

refs #38294

diff --git src/hg/js/hgBlat.js src/hg/js/hgBlat.js
index 178b986220d..dd80fbc5c6f 100644
--- src/hg/js/hgBlat.js
+++ src/hg/js/hgBlat.js
@@ -268,30 +268,40 @@
 function blatApplyTooltips() {
     $('#blatTable thead th').each(function() {
         var tip = BLAT_HEADER_TIPS[$(this).text().trim()];
         if (tip) { $(this).attr('title', tip); }
     });
     if (typeof convertTitleTagsToMouseovers === 'function') {
         convertTitleTagsToMouseovers();
     }
 }
 
 // ---- share a link --------------------------------------------------------
 
 // The snapshot link we created for this page view, cached so re-opening the box doesn't make another.
 var blatShareCachedUrl = null;
 
+// True while the create request is on its way.  The cached URL only exists once the reply is in, so
+// without this a second click during the wait mints a second snapshot session for the same results.
+var blatShareBusy = false;
+
+// Is the share box on screen?  A reply that arrives after the user has clicked the button again to
+// dismiss the box must not reopen it.
+function blatShareBoxOpen(box) {
+    return box.style.display === 'flex';
+}
+
 // Render the share box.  url set -> show the link + Copy; url null -> "Creating link…"; msg (url null)
 // -> show an error.
 function blatShowShareBox(box, url, msg) {
     box.style.display = 'flex';
     if (msg) {
         box.innerHTML = '<span class="gbShareMsg gbShareFull" style="color:#a00">' +
             htmlEncode(msg) + '</span>';
         return;
     }
     if (!url) {
         box.innerHTML = '<span class="gbShareMsg gbShareFull">Creating link…</span>';
         return;
     }
     box.innerHTML =
         '<span class="gbShareMsg gbShareFull">Anyone with this <b>shareable link</b> can open ' +
@@ -315,48 +325,58 @@
 
 function blatShareLink() {
     // Create (or reveal) a durable share link.  It is backed by a lightweight "snapshot" session that
     // stores only db + the results bigPsl - not the whole cart - under a server-generated unique name
     // (see lib/snapshotSession.c).  hgBlat's ?u=&s= reopen (doShareReopen) rebuilds the results table
     // from that bigPsl.  The token generation, uniqueness and cleanup are shared with hgc and the
     // top-right "Share a link".
     var box = document.getElementById('gbShareBox');
     if (!box) { return; }
     if (box.style.display === 'flex') { box.style.display = 'none'; return; }   // toggle off
 
     // Already viewing a shared session link: the current URL is itself the shareable link.
     if (/[?&]s=/.test(window.location.search)) { blatShowShareBox(box, window.location.href); return; }
     // Already created one this page view: reuse it rather than creating another session.
     if (blatShareCachedUrl) { blatShowShareBox(box, blatShareCachedUrl); return; }
+    // One is already being created: show it working again instead of asking for a second one.
+    if (blatShareBusy) { blatShowShareBox(box, null); return; }
 
     var cfg = hgBlatData.config;
+    blatShareBusy = true;
     blatShowShareBox(box, null);   // "Creating link…"
     var body = 'hgsid=' + encodeURIComponent(cfg.hgsid || '') +
         '&hgS_doSaveSessionJson=1&hgS_shareAnon=1&hgS_snapshotType=blat';
     fetch('../cgi-bin/hgSession', {method: 'POST', credentials: 'same-origin',
             headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: body})
         .then(function(r) { return r.json(); })
         .then(function(data) {
+            blatShareBusy = false;
             if (!data || !data.name) {
+                if (blatShareBoxOpen(box))
                     blatShowShareBox(box, null, (data && data.error) || 'Could not create the link.');
                 return;
             }
             blatShareCachedUrl = window.location.origin + '/cgi-bin/hgBlat?u=l&s=' +
                 encodeURIComponent(data.name);
+            // Keep a box the user dismissed while waiting closed; the link is cached for the
+            // next click on the button.
+            if (blatShareBoxOpen(box))
                 blatShowShareBox(box, blatShareCachedUrl);
         })
         .catch(function() {
+            blatShareBusy = false;
+            if (blatShareBoxOpen(box))
                 blatShowShareBox(box, null, 'Could not reach the server. Please try again.');
         });
 }
 
 // ---- Rename BLAT track (modal) -------------------------------------------
 // The results custom track is built (and renamed) by hgBlat.c's inline code, which exposes a small
 // window.blatRenameCt(name, description) helper (it POSTs to hgc's buildBigPsl and rebuilds the
 // track).  We reuse that helper (no new endpoint), just swapping its old inline toggle-form UI for a
 // proper modal dialog.  The current name/description come from cfg (hgBlat.c), not a global, so this
 // does not depend on any generic page-global.
 
 function blatRenameModalHtml(cfg) {
     // hgSession link is relative (same /cgi-bin/), carrying db + hgsid so the session page opens in
     // this assembly and cart.
     var sessionUrl = `hgSession?db=${encodeURIComponent(cfg.db)}&hgsid=${encodeURIComponent(cfg.hgsid)}`;