From b2449d3139f420ff6bdcbd32536ae88524e2c0e1 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 18 May 2026 15:45:22 +0000 Subject: [PATCH] fix(nav,footer): drop orphan _hidden section header, show footer on Activate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/gui/app.py | 9 ++++++--- src/gui/components/_legacy.py | 30 +++++++++++++++--------------- src/gui/pages/_Activate.py | 7 ++++++- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index 0270ddc..ad31b80 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -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], } diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index 5e3214a..d364f9c 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -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, diff --git a/src/gui/pages/_Activate.py b/src/gui/pages/_Activate.py index bd4c7bd..22b26b5 100644 --- a/src/gui/pages/_Activate.py +++ b/src/gui/pages/_Activate.py @@ -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")