fix(footer): Close button now actually fires — wrong testid + bad fallback

Two bugs combined to make the footer Close a no-op:

1. The helper page_link's anchor carries
   ``data-testid="stPageLink-NavLink"`` — the bare
   ``stPageLink`` testid is on the OUTER WRAPPER div, not the
   anchor. The old selector ``a[data-testid="stPageLink"]``
   matched nothing, so ``helper`` was always ``null``.
2. The fallback ``window.location.href = './close'`` ran inside
   the component iframe, so it only navigated the (invisible)
   srcdoc iframe. The main app stayed put.

End result: click → nothing visible → shutdown_app never runs →
farewell-script's ``window.close()`` attempt never happens →
user sees the Close button as broken.

Fixes:
- Selector → ``a[data-testid="stPageLink-NavLink"][href*="close"]``.
  ``href*="close"`` covers both root (/close) and base-path
  (/myapp/close) deployments.
- Fallback → resolve the parent window via
  ``doc.defaultView`` (the parent doc's window) with a
  ``window.top`` fallback, so the hard-nav navigates the whole
  app instead of just the iframe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 16:02:46 +00:00
parent b568773a1f
commit add3b866ee

View File

@@ -815,17 +815,29 @@ a[data-testid="stPageLink"][href$="/close/"] {
// reload, no websocket churn. Fall back to a hard nav if the // reload, no websocket churn. Fall back to a hard nav if the
// helper link hasn't rendered yet (first paint race) so the // helper link hasn't rendered yet (first paint race) so the
// button is never a no-op. // button is never a no-op.
//
// The page_link's anchor uses ``data-testid="stPageLink-NavLink"``
// (the outer wrapper div carries the bare ``stPageLink`` testid;
// dispatching click on the wrapper doesn't fire Streamlit's
// React onClick handler). ``href*="close"`` covers both root
// (/close) and base-path (e.g. /myapp/close) deployments.
closeBtn.addEventListener('click', function (e) {{ closeBtn.addEventListener('click', function (e) {{
e.preventDefault(); e.preventDefault();
var helper = doc.querySelector( var helper = doc.querySelector(
'a[data-testid="stPageLink"][href$="/close"], ' + 'a[data-testid="stPageLink-NavLink"][href*="close"]'
'a[data-testid="stPageLink"][href$="/close/"]'
); );
if (helper) {{ if (helper) {{
helper.click(); helper.click();
}} else {{ return;
window.location.href = './close';
}} }}
// Hard-nav fallback. ``window`` inside this script is the
// component iframe's window — changing ITS location only
// navigates the iframe (which lives in srcdoc and is
// invisible). Use the parent doc's location so the whole
// app navigates.
var topWin = (doc.defaultView) || window.parent || window.top || window;
try {{ topWin.location.href = './close'; }}
catch (err) {{ window.top.location.href = './close'; }}
}}); }});
div.appendChild(helpBtn); div.appendChild(helpBtn);