0cdd15681f10789132dc9e88fcf4be23bd47ee4b
max
  Wed Sep 9 09:14:37 2026 -0700
New Sessions page: Replace on the save card keeps the session's sharing level

Confirming Replace re-saved through the save endpoint, which always writes a
session as shared by link.  Replacing a session that was in the public listing
took it off the list, and replacing a private one made it loadable by anyone
with the link.  Replace now goes through the overwrite endpoint, which reads the
row's sharing level and keeps it - the same endpoint the floppy button on each
table row already uses.  The description and the "only I can load it" box on
the save card are still applied afterwards, refs #38311

diff --git src/hg/js/hgSession.js src/hg/js/hgSession.js
index 0ed45401fa3..15160be5b58 100644
--- src/hg/js/hgSession.js
+++ src/hg/js/hgSession.js
@@ -486,41 +486,41 @@
         var rand = sessRandomShareName();
         sessConfirm({
             title: 'Save without a name?',
             bodyHtml: 'You left the session name empty. Your session will be saved under the ' +
                 'randomly generated name <b>' + sessEnc(rand) + '</b>.<br><br>You can also create ' +
                 'these quick share links any time from the <b>Share a link</b> option at the top ' +
                 'right of every Genome Browser page.',
             okLabel: 'Save session',
             onOk: function() { sessModalClose(); sessDoSaveWithName(rand); }
         });
         return;
     }
     sessDoSaveWithName(name);
 }
 
-function sessDoSaveWithName(name, allowOverwrite) {
+function sessDoSaveWithName(name) {
     var priv = document.getElementById('sessSavePrivate').checked;
     var descEl = document.getElementById('sessSaveDesc');
     var desc = descEl ? descEl.value.trim() : '';
     var p = {};
     p[SESS_ACT.save] = '1';
     p[SESS_P.newName] = name;
     // Saving under a name you are already using replaces that session's contents, so ask first.
     // With failIfExists set the CGI answers {exists: true} instead of saving, which is how the
     // top-right "Share a link" menu handles the same collision.
-    if (!allowOverwrite) { p[SESS_P.failIfExists] = '1'; }
+    p[SESS_P.failIfExists] = '1';
 
     // doSaveSessionJson always saves shared-by-link (the default); chain the optional description
     // and, if the user asked for "only I can load it", make it private, then reload to show the row.
     // The session is saved by the time those run, so a failure in one of them has to be reported: a
     // silent reload would leave a session sitting there shared by link, or with no description, and
     // tell the user nothing.
     function reload() { window.location.reload(); }
     function partlySaved(problem) {
         sessAlert('Session saved, with a problem',
                   'Your session <b>' + sessEnc(name) + '</b> was saved, but ' + sessEnc(problem),
                   reload);
     }
     function afterDesc() {
         if (!priv) { reload(); return; }
         var sp = {};
@@ -530,39 +530,51 @@
         sessAjax(sp, reload, function(m) {
             partlySaved('it could not be made private, so anyone with the link can still load it. ' +
                         m);
         });
     }
     function afterSave() {
         if (!desc) { afterDesc(); return; }
         var dp = {};
         dp[SESS_ACT.describe] = '1';
         dp[SESS_P.oldName] = name;
         dp[SESS_P.descr] = desc;
         sessAjax(dp, afterDesc, function(m) {
             partlySaved('the description could not be saved. ' + m);
         });
     }
+    function replace() {
+        // Replace goes through the overwrite endpoint rather than saving again.  A save always
+        // writes the session as shared by link, which would take a session out of the public
+        // gallery, or make a private one loadable by anyone holding the link.  Overwrite keeps
+        // whatever sharing level the session already had, and the chained steps below still apply
+        // the description and the "only I can load it" box if the user set them.
+        var op = {};
+        op[SESS_ACT.overwrite] = '1';
+        op[SESS_P.oldName] = name;
+        sessAjax(op, afterSave);
+    }
     sessAjax(p, function(resp) {
         if (resp && resp.exists) {
             sessConfirm({
                 title: 'Replace this session?',
                 bodyHtml: 'You already have a session named <b>' + sessEnc(name) + '</b>. Replacing ' +
                     'it points that name at the view you are looking at now, and what the session ' +
-                    'held before is gone.',
+                    'held before is gone.' +
+                    (priv ? '' : ' Who can load it stays as it is.'),
                 okLabel: 'Replace it',
-                onOk: function() { sessModalClose(); sessDoSaveWithName(name, true); }
+                onOk: function() { sessModalClose(); replace(); }
             });
             return;
         }
         afterSave();
     });
 }
 
 // ---- Advanced panel (navigation forms) ----------------------------------
 
 function sessAdvancedHtml(C) {
     var sid = '<input type="hidden" name="' + sessEnc(C.cartVar) + '" value="' + sessEnc(C.hgsid) + '">';
     var loadUser = '';
     if (C.loggedIn) {
         loadUser =
         '<div class="sessAdvItem"><span class="lab">Load another user’s session</span>' +