fix(nav,footer): drop orphan _hidden section header, show footer on Activate

Two follow-ups to the prior sidebar/footer cleanup:

- The "_hidden" section header was still visible in the sidebar
  because Streamlit renders ``stNavSectionHeader`` as a sibling of
  ``stNavSection``, not a child — so the ``:has()`` rule on the
  section was hiding the items list but leaving the header
  (and its collapse/drilldown marker) behind. Move Activate +
  Close into the unlabeled section (key ``""``) alongside Home so
  there is no header to leak in the first place, then hide just
  the two links via ``stSidebarNavLinkContainer:has(...)`` (with
  a defensive ``a[href$=...]`` fallback for browsers without
  ``:has()`` support).
- The sticky footer was missing on ``pages/_Activate.py`` because
  the page never called ``render_sticky_footer`` — added the
  call so the Help / Close bar persists when the user follows
  the popover's Activate / Manage link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 15:45:22 +00:00
parent d840230e48
commit b2449d3139
3 changed files with 27 additions and 19 deletions

View File

@@ -109,13 +109,16 @@ def _build_navigation() -> dict[str, list]:
url_path="close",
)
# Activate + Close are placed in the unlabeled section alongside
# Home (key ``""`` — Streamlit renders no header for it). The CSS
# in ``hide_streamlit_chrome`` then hides just their two links by
# ``href``, leaving Home visible and no orphan section header /
# drilldown marker in the sidebar.
return {
"": [home],
"": [home, activate, close],
section_label("cleaners"): by_section["cleaners"],
section_label("transformations"): by_section["transformations"],
section_label("automations"): by_section["automations"],
# Hidden section — see comment above + CSS in hide_streamlit_chrome.
"_hidden": [activate, close],
}

View File

@@ -60,29 +60,29 @@ header[data-testid="stHeader"] {
footer {
display: none !important;
}
/* Hide the Activate + Close entries from the sidebar nav — both
/* Hide the Activate + Close entries from the sidebar nav. Both
pages stay registered (so /activate and /close remain
URL-routable) but are reached from the sticky-footer Help
popover instead of the sidebar. */
popover instead of the sidebar. They are grouped under the
unlabeled section alongside Home in ``app.py`` so hiding the
two links here leaves no orphan section header behind. We
target the LinkContainer (Streamlit's per-entry wrapper) so the
list item collapses, not just the inner anchor — otherwise the
container's spacing would still occupy a row. */
[data-testid="stSidebarNav"] [data-testid="stSidebarNavLinkContainer"]:has(a[href$="/activate"]),
[data-testid="stSidebarNav"] [data-testid="stSidebarNavLinkContainer"]:has(a[href$="/activate/"]),
[data-testid="stSidebarNav"] [data-testid="stSidebarNavLinkContainer"]:has(a[href$="/close"]),
[data-testid="stSidebarNav"] [data-testid="stSidebarNavLinkContainer"]:has(a[href$="/close/"]) {
display: none !important;
}
/* Defensive fallback for browsers without :has() support — at
least hide the anchor itself so the entry isn't clickable. */
[data-testid="stSidebarNav"] a[href$="/activate"],
[data-testid="stSidebarNav"] a[href$="/activate/"],
[data-testid="stSidebarNav"] a[href$="/close"],
[data-testid="stSidebarNav"] a[href$="/close/"] {
display: none !important;
}
/* Hide the section header that wraps those entries so no orphan
label is left above the hidden links. Streamlit tags each nav
section with ``data-testid="stNavSection"``; the :has() selector
picks only the one(s) containing those links. Modern browsers
(Chrome 105+, Safari 15.4+, Firefox 121+) all support :has();
older browsers fall back to showing the section header, which
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/"]) {
display: none !important;
}
/* Reclaim top padding lost from hidden header. Slim the bottom too —
Streamlit's default leaves several rems below the last widget. */
.stAppViewBlockContainer,

View File

@@ -19,7 +19,11 @@ _project_root = Path(__file__).resolve().parent.parent.parent.parent
if str(_project_root) not in sys.path:
sys.path.insert(0, str(_project_root))
from src.gui.components import hide_streamlit_chrome, render_activation_form
from src.gui.components import (
hide_streamlit_chrome,
render_activation_form,
render_sticky_footer,
)
from src.i18n import t
st.set_page_config(
@@ -31,4 +35,5 @@ st.set_page_config(
# ``gate_license=False`` keeps the chrome from re-rendering the
# activation form on top of the form we're about to render below.
hide_streamlit_chrome(gate_license=False)
render_sticky_footer()
render_activation_form(key_prefix="page")