From e885be599095037f93209d42ba55c77c2fb6b6ee Mon Sep 17 00:00:00 2001 From: Tyler Date: Sun, 17 May 2026 00:48:26 -0700 Subject: Add session persistence and watchlist exit button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Persists watchlist and active ticker across browser refreshes via a localStorage bridge (components/persistence.py), and adds a sidebar "← Watchlist" button to clear the active ticker and return to the QuoteTable landing page. Co-Authored-By: Claude Sonnet 4.6 --- app.py | 12 ++++++++++- components/persistence.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 components/persistence.py diff --git a/app.py b/app.py index e87482d..b0f3add 100644 --- a/app.py +++ b/app.py @@ -406,7 +406,9 @@ hr { /* Hide click receiver inputs without removing them from React's event system */ [data-testid="stSidebar"] [data-testid="stTextInput"]:has(input[aria-label="wl_click_receiver"]), -[data-testid="stTextInput"]:has(input[aria-label="qt_click_receiver"]) { +[data-testid="stTextInput"]:has(input[aria-label="qt_click_receiver"]), +[data-testid="stTextInput"]:has(input[aria-label="persist_wl"]), +[data-testid="stTextInput"]:has(input[aria-label="persist_tk"]) { visibility: hidden !important; height: 0 !important; min-height: 0 !important; @@ -590,6 +592,7 @@ _prism_template = go.layout.Template(layout=_prism_layout) pio.templates["prism"] = _prism_template pio.templates.default = "prism" +from components.persistence import render_persistence_bridge from components.market_bar import render_market_bar from components.top_movers import render_top_movers from components.watchlist import render_watchlist @@ -614,6 +617,7 @@ if "watchlist" not in st.session_state: if "active_tab" not in st.session_state: st.session_state["active_tab"] = "overview" +render_persistence_bridge() # ── Sidebar ─────────────────────────────────────────────────────────────────── @@ -719,6 +723,12 @@ with st.sidebar: ticker = st.session_state["ticker"] + # ── Exit to landing page ────────────────────────────────────────────── + if st.session_state.get("ticker"): + if st.button("← Watchlist", key="exit_ticker", use_container_width=True): + st.session_state["ticker"] = None + st.rerun() + # ── Workspace nav ───────────────────────────────────────────────────── st.markdown("
Workspace
", unsafe_allow_html=True) _nav = [ diff --git a/components/persistence.py b/components/persistence.py new file mode 100644 index 0000000..1dafdbe --- /dev/null +++ b/components/persistence.py @@ -0,0 +1,54 @@ +import json +import streamlit as st +import streamlit.components.v1 as components +from utils.security import escape_html + + +def render_persistence_bridge() -> None: + """Sync watchlist + ticker to/from localStorage.""" + restored_wl = st.session_state.get("_persist_wl", "") + restored_tk = st.session_state.get("_persist_tk", "") + if restored_wl and not st.session_state.get("_persist_loaded"): + try: + st.session_state["watchlist"] = json.loads(restored_wl) + except Exception: + pass + if restored_tk: + st.session_state["ticker"] = restored_tk + st.session_state["_persist_loaded"] = True + st.rerun() + + st.text_input("persist_wl", key="_persist_wl", label_visibility="collapsed") + st.text_input("persist_tk", key="_persist_tk", label_visibility="collapsed") + + wl_json = escape_html(json.dumps(st.session_state.get("watchlist", []))) + tk_val = escape_html(st.session_state.get("ticker") or "") + + html = ( + "
" + "" + ) + components.html(html, height=0, scrolling=False) -- cgit v1.3-2-g0d8e