From 7cb1bc922d7f1eb7f05b1e288d06f9092e01ef34 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 17 May 2026 02:31:50 +0000 Subject: [PATCH] =?UTF-8?q?fix(nav):=20restore=20real=20Streamlit=20Back-t?= =?UTF-8?q?o-Home=20button=20=E2=80=94=20preserves=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported: after the sticky-footer href fix (be7191a) the back-to-home click worked but the home-page upload list disappeared. Full-page navigation via ```` doesn't preserve ``st.session_state`` on the user's Streamlit build. Trade-off forced: pick visible-from-anywhere sticky footer OR state preservation. Can't have both because ``st.switch_page`` (soft nav, preserves state) needs a real Streamlit button widget, and Streamlit widgets can't be reliably CSS-positioned to the viewport bottom — Streamlit owns the widget DOM and remounts it on every rerun. State preservation wins. Going back to the pre-sticky design: - ``render_sticky_footer()`` becomes a no-op shim. Kept as a callable so the call sites in every tool page don't have to be touched in this commit; the original implementation is preserved as ``_render_sticky_footer_DISABLED`` if we ever decide to revisit. - Every Ready/Coming-Soon tool page (1-9) gets ``back_to_home_link()`` reinstated near the top of the page (visible at scroll-top) AND ``back_to_home_link(key="_back_to_home_link_bottom")`` reinstated near the bottom of the page (visible at scroll-bottom). Both instances call ``st.switch_page`` via the existing helper — soft nav, no full reload, ``st.session_state["home_uploads"]`` and every other session-state key survive. User trades the "always-visible while scrolling" sticky behavior for the upload-list-survives-navigation behavior. The two-button pattern (top + bottom) was what we had before the sticky-footer experiment; on short pages both are visible at once, on long pages the user has one in reach at either end. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/gui/components/_legacy.py | 26 ++++++++++++++++++++++++++ src/gui/pages/1_Deduplicator.py | 3 +++ src/gui/pages/2_Text_Cleaner.py | 3 +++ src/gui/pages/3_Format_Standardizer.py | 3 +++ src/gui/pages/4_Missing_Values.py | 3 +++ src/gui/pages/5_Column_Mapper.py | 3 +++ src/gui/pages/6_Outlier_Detector.py | 3 +++ src/gui/pages/7_Multi_File_Merger.py | 3 +++ src/gui/pages/8_Validator_Reporter.py | 3 +++ src/gui/pages/9_Pipeline_Runner.py | 3 +++ 10 files changed, 53 insertions(+) diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index 6903762..edb38bf 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -521,6 +521,32 @@ html_download_button = local_download_button def render_sticky_footer() -> None: + """No-op shim. + + The previous implementation injected a CSS-positioned ```` + footer into the page body. It looked nice (always-visible bar at + the viewport bottom) but the click was a FULL-page navigation, + which on the user's Streamlit build doesn't preserve + ``st.session_state`` — uploads disappeared after every Back to + Home click. Soft navigation via ``st.switch_page`` requires a + real Streamlit button widget, which we can't reliably + CSS-position to the viewport bottom because Streamlit owns the + widget's DOM container and remounts it on every rerun. + + So the sticky footer is retired. Each tool page renders the + real Streamlit-button version of ``back_to_home_link`` near the + top AND near the bottom (the two-instance pattern from before + the sticky-footer attempt), so the button is visible at both + ends of the page without ever risking state loss. + + Kept as a callable so existing tool-page imports + call sites + don't have to be touched in this commit — they all resolve to + this no-op. + """ + return + + +def _render_sticky_footer_DISABLED() -> None: """Slim fixed-position footer at the bottom of the viewport. Contains a "Back to Home" link that's always visible regardless of diff --git a/src/gui/pages/1_Deduplicator.py b/src/gui/pages/1_Deduplicator.py index ed03144..4875403 100644 --- a/src/gui/pages/1_Deduplicator.py +++ b/src/gui/pages/1_Deduplicator.py @@ -33,6 +33,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("1_Deduplicator") require_feature_or_render_upgrade(FeatureFlag.DEDUPLICATOR) @@ -405,6 +406,8 @@ else: # Footer # --------------------------------------------------------------------------- +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption( "Runs locally. Your data never leaves this computer. " diff --git a/src/gui/pages/2_Text_Cleaner.py b/src/gui/pages/2_Text_Cleaner.py index 495a631..27fbd9e 100644 --- a/src/gui/pages/2_Text_Cleaner.py +++ b/src/gui/pages/2_Text_Cleaner.py @@ -35,6 +35,7 @@ from src.core.text_clean import ( hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("2_Text_Cleaner") require_feature_or_render_upgrade(FeatureFlag.TEXT_CLEANER) @@ -382,6 +383,8 @@ with dl_c: mime="application/json", ) +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0") diff --git a/src/gui/pages/3_Format_Standardizer.py b/src/gui/pages/3_Format_Standardizer.py index 35d7ab1..00bd70c 100644 --- a/src/gui/pages/3_Format_Standardizer.py +++ b/src/gui/pages/3_Format_Standardizer.py @@ -33,6 +33,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("3_Format_Standardizer") require_feature_or_render_upgrade(FeatureFlag.FORMAT_STANDARDIZER) @@ -652,6 +653,8 @@ with dl_c: mime="application/json", ) +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0") diff --git a/src/gui/pages/4_Missing_Values.py b/src/gui/pages/4_Missing_Values.py index ee997af..9f912e5 100644 --- a/src/gui/pages/4_Missing_Values.py +++ b/src/gui/pages/4_Missing_Values.py @@ -34,6 +34,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("4_Missing_Values") require_feature_or_render_upgrade(FeatureFlag.MISSING_HANDLER) @@ -415,6 +416,8 @@ with dl_c: mime="application/json", ) +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0") diff --git a/src/gui/pages/5_Column_Mapper.py b/src/gui/pages/5_Column_Mapper.py index 38b28e7..dc082a5 100644 --- a/src/gui/pages/5_Column_Mapper.py +++ b/src/gui/pages/5_Column_Mapper.py @@ -35,6 +35,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("5_Column_Mapper") require_feature_or_render_upgrade(FeatureFlag.COLUMN_MAPPER) @@ -459,6 +460,8 @@ with dl_c: mime="application/json", ) +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0") diff --git a/src/gui/pages/6_Outlier_Detector.py b/src/gui/pages/6_Outlier_Detector.py index e878877..bc5ee09 100644 --- a/src/gui/pages/6_Outlier_Detector.py +++ b/src/gui/pages/6_Outlier_Detector.py @@ -22,6 +22,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("6_Outlier_Detector") require_feature_or_render_upgrade(FeatureFlag.OUTLIER_DETECTOR) @@ -100,6 +101,8 @@ st.button("Detect Outliers", type="primary", use_container_width=True, disabled= # Footer # --------------------------------------------------------------------------- +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption( "Runs locally. Your data never leaves this computer. " diff --git a/src/gui/pages/7_Multi_File_Merger.py b/src/gui/pages/7_Multi_File_Merger.py index dc94ce4..2aa838e 100644 --- a/src/gui/pages/7_Multi_File_Merger.py +++ b/src/gui/pages/7_Multi_File_Merger.py @@ -22,6 +22,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("7_Multi_File_Merger") require_feature_or_render_upgrade(FeatureFlag.MULTI_FILE_MERGER) @@ -98,6 +99,8 @@ st.button("Merge Files", type="primary", use_container_width=True, disabled=True # Footer # --------------------------------------------------------------------------- +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption( "Runs locally. Your data never leaves this computer. " diff --git a/src/gui/pages/8_Validator_Reporter.py b/src/gui/pages/8_Validator_Reporter.py index c0ee773..c1a8f20 100644 --- a/src/gui/pages/8_Validator_Reporter.py +++ b/src/gui/pages/8_Validator_Reporter.py @@ -22,6 +22,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("8_Validator_Reporter") require_feature_or_render_upgrade(FeatureFlag.VALIDATOR_REPORTER) @@ -105,6 +106,8 @@ st.button("Validate & Generate Report", type="primary", use_container_width=True # Footer # --------------------------------------------------------------------------- +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption( "Runs locally. Your data never leaves this computer. " diff --git a/src/gui/pages/9_Pipeline_Runner.py b/src/gui/pages/9_Pipeline_Runner.py index d598133..b4a26c4 100644 --- a/src/gui/pages/9_Pipeline_Runner.py +++ b/src/gui/pages/9_Pipeline_Runner.py @@ -36,6 +36,7 @@ from src.license import FeatureFlag hide_streamlit_chrome() render_sticky_footer() +back_to_home_link() from src.audit import log_page_open log_page_open("9_Pipeline_Runner") require_feature_or_render_upgrade(FeatureFlag.PIPELINE_RUNNER) @@ -417,6 +418,8 @@ with dl_c: mime="application/json", ) +back_to_home_link(key="_back_to_home_link_bottom") + st.divider() st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")