fix(nav,footer): hide Activate from sidebar, surface it in Help popover

- Collapse the Account section: Activate now lives in the same
  hidden sidebar section as Close (single ``_hidden`` group). Both
  pages stay registered with ``st.navigation`` so /activate and
  /close remain URL-routable for the Help-popover / Close-button
  links — only the sidebar entries + their section header are
  hidden via CSS.
- Help popover always exposes a license-management link now:
  ``Activate now →`` when the license is inactive, ``Manage
  license →`` when it is active and valid. Both point at
  ``./activate``.
- Extend the sidebar-hide CSS to also match ``a[href$="/activate"]``
  and the section that contains it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 15:39:14 +00:00
parent 9e8b4b2ca9
commit d840230e48
4 changed files with 30 additions and 19 deletions

View File

@@ -88,17 +88,20 @@ def _build_navigation() -> dict[str, list]:
default=True, default=True,
url_path="home", url_path="home",
) )
# Activate + Close are both reached from the sticky-footer Help
# popover (Activate / Manage-license link and Close button). They
# are registered with ``st.navigation`` so ``/activate`` and
# ``/close`` remain URL-routable — pages not listed in the dict
# 404 even by direct hit — but their sidebar entries are hidden
# via CSS in ``hide_streamlit_chrome``. We group them under a
# single section header that is also hidden by that same CSS rule
# so no orphan "Account" / "Close" label is left in the sidebar.
activate = st.Page( activate = st.Page(
"pages/_Activate.py", "pages/_Activate.py",
title=_t("nav.activate_title") or "Activate", title=_t("nav.activate_title") or "Activate",
icon="🔑", icon="🔑",
url_path="activate", url_path="activate",
) )
# Close is registered so it remains URL-routable (/close) for the
# sticky-footer Close button. It is hidden from the sidebar via CSS
# in ``hide_streamlit_chrome`` rather than being unregistered —
# ``st.navigation`` requires every routable page to be listed; pages
# not in the dict 404 even by direct URL hit.
close = st.Page( close = st.Page(
"pages/99_Close.py", "pages/99_Close.py",
title=_t("nav.close_title") or "Close", title=_t("nav.close_title") or "Close",
@@ -106,15 +109,13 @@ def _build_navigation() -> dict[str, list]:
url_path="close", url_path="close",
) )
account_header = _t("nav.section_account") or "Account"
close_header = _t("nav.section_close") or "Close"
return { return {
"": [home], "": [home],
section_label("cleaners"): by_section["cleaners"], section_label("cleaners"): by_section["cleaners"],
section_label("transformations"): by_section["transformations"], section_label("transformations"): by_section["transformations"],
section_label("automations"): by_section["automations"], section_label("automations"): by_section["automations"],
account_header: [activate], # Hidden section — see comment above + CSS in hide_streamlit_chrome.
close_header: [close], "_hidden": [activate, close],
} }

View File

@@ -60,20 +60,25 @@ header[data-testid="stHeader"] {
footer { footer {
display: none !important; display: none !important;
} }
/* Hide the "Close" entry from the sidebar nav — the page is kept /* Hide the Activate + Close entries from the sidebar nav — both
registered so the sticky-footer Close link can still route to pages stay registered (so /activate and /close remain
/close, but the sidebar entry is redundant and risks mis-clicks. */ URL-routable) but are reached from the sticky-footer Help
popover instead of the sidebar. */
[data-testid="stSidebarNav"] a[href$="/activate"],
[data-testid="stSidebarNav"] a[href$="/activate/"],
[data-testid="stSidebarNav"] a[href$="/close"], [data-testid="stSidebarNav"] a[href$="/close"],
[data-testid="stSidebarNav"] a[href$="/close/"] { [data-testid="stSidebarNav"] a[href$="/close/"] {
display: none !important; display: none !important;
} }
/* Hide the entire "Close" section so its header label doesn't /* Hide the section header that wraps those entries so no orphan
orphan above the hidden link. Streamlit tags each nav section label is left above the hidden links. Streamlit tags each nav
with ``data-testid="stNavSection"``; the :has() selector picks section with ``data-testid="stNavSection"``; the :has() selector
only the one containing the close link. Modern browsers (Chrome picks only the one(s) containing those links. Modern browsers
105+, Safari 15.4+, Firefox 121+) all support it; older (Chrome 105+, Safari 15.4+, Firefox 121+) all support :has();
browsers fall back to showing the empty header, which is older browsers fall back to showing the section header, which
visually harmless. */ is visually harmless. */
[data-testid="stSidebarNav"] [data-testid="stNavSection"]:has(a[href$="/activate"]),
[data-testid="stSidebarNav"] [data-testid="stNavSection"]:has(a[href$="/activate/"]),
[data-testid="stSidebarNav"] [data-testid="stNavSection"]:has(a[href$="/close"]), [data-testid="stSidebarNav"] [data-testid="stNavSection"]:has(a[href$="/close"]),
[data-testid="stSidebarNav"] [data-testid="stNavSection"]:has(a[href$="/close/"]) { [data-testid="stSidebarNav"] [data-testid="stNavSection"]:has(a[href$="/close/"]) {
display: none !important; display: none !important;
@@ -593,11 +598,14 @@ def render_sticky_footer() -> None:
date=(state.expires_at or "")[:10], date=(state.expires_at or "")[:10],
days=state.days_remaining, days=state.days_remaining,
) )
manage_link = _html.escape(_t("footer.help_manage_link"))
license_html = ( license_html = (
f'<div class="dt-help-row"><span class="dt-help-key">' f'<div class="dt-help-row"><span class="dt-help-key">'
f'{license_label}:</span> {_html.escape(active_line)}</div>' f'{license_label}:</span> {_html.escape(active_line)}</div>'
f'<div class="dt-help-row dt-help-sub">' f'<div class="dt-help-row dt-help-sub">'
f'{_html.escape(expires_line)}</div>' f'{_html.escape(expires_line)}</div>'
f'<div class="dt-help-row">'
f'<a href="./activate" target="_self">{manage_link}</a></div>'
) )
else: else:
inactive_line = _html.escape(_t("footer.help_license_inactive")) inactive_line = _html.escape(_t("footer.help_license_inactive"))

View File

@@ -183,6 +183,7 @@
"help_license_active": "{name}", "help_license_active": "{name}",
"help_license_expires": "Expires {date} ({days} days)", "help_license_expires": "Expires {date} ({days} days)",
"help_activate_link": "Activate now →", "help_activate_link": "Activate now →",
"help_manage_link": "Manage license →",
"help_dismiss": "Close" "help_dismiss": "Close"
} }
} }

View File

@@ -183,6 +183,7 @@
"help_license_active": "{name}", "help_license_active": "{name}",
"help_license_expires": "Caduca {date} ({days} días)", "help_license_expires": "Caduca {date} ({days} días)",
"help_activate_link": "Activar ahora →", "help_activate_link": "Activar ahora →",
"help_manage_link": "Gestionar licencia →",
"help_dismiss": "Cerrar" "help_dismiss": "Cerrar"
} }
} }