diff options
| author | Tyler <tyler@tylerhoang.xyz> | 2026-05-17 00:58:25 -0700 |
|---|---|---|
| committer | Tyler <tyler@tylerhoang.xyz> | 2026-05-17 00:58:25 -0700 |
| commit | 48f3bc1960d71785aad44292205845612a4f1cb8 (patch) | |
| tree | 10e2d10ded75523b7e3de3c4ac925ff0e2979b62 | |
| parent | e885be599095037f93209d42ba55c77c2fb6b6ee (diff) | |
Fix persistence write-before-read and click swallow bugs
Two root causes:
1. The JS wrote empty session defaults to localStorage before reading
stored values back, destroying saved data on every fresh page load.
Fixed by gating on data-loaded: JS reads+restores only when loaded=0,
writes+saves only when loaded=1.
2. st.rerun() inside render_persistence_bridge() aborted the render
before qt_click_receiver/wl_click_receiver were registered. Streamlit
clears unrendered widget state, so pending click values (_qt_click,
_wl_click) were wiped before quotetable/watchlist could process them.
Fixed by removing st.rerun() — the bridge runs at the top of the
render cycle so restored session state propagates to components below
in the same pass; no extra rerun needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | components/persistence.py | 28 |
1 files changed, 18 insertions, 10 deletions
diff --git a/components/persistence.py b/components/persistence.py index 1dafdbe..552347d 100644 --- a/components/persistence.py +++ b/components/persistence.py @@ -16,25 +16,24 @@ def render_persistence_bridge() -> None: if restored_tk: st.session_state["ticker"] = restored_tk st.session_state["_persist_loaded"] = True - st.rerun() + # No st.rerun() — the bridge runs at the top of the render cycle so the + # restored values propagate naturally to components below. Calling + # st.rerun() here would abort the render before click-receiver inputs + # (qt_click_receiver, wl_click_receiver) are registered, causing + # Streamlit to clear their pending values and swallow the click. st.text_input("persist_wl", key="_persist_wl", label_visibility="collapsed") st.text_input("persist_tk", key="_persist_tk", label_visibility="collapsed") + loaded = "1" if st.session_state.get("_persist_loaded") else "0" wl_json = escape_html(json.dumps(st.session_state.get("watchlist", []))) tk_val = escape_html(st.session_state.get("ticker") or "") html = ( - "<div id='d' data-wl='" + wl_json + "' data-tk='" + tk_val + "'></div>" + "<div id='d' data-loaded='" + loaded + "' data-wl='" + wl_json + "' data-tk='" + tk_val + "'></div>" "<script>" "(function() {" " var d = document.getElementById('d');" - " localStorage.setItem('prism_watchlist', d.dataset.wl);" - " if (d.dataset.tk) localStorage.setItem('prism_ticker', d.dataset.tk);" - " else localStorage.removeItem('prism_ticker');" - " var stored_wl = localStorage.getItem('prism_watchlist');" - " var stored_tk = localStorage.getItem('prism_ticker') || '';" - " if (!stored_wl) return;" " function setInput(label, val) {" " var inputs = window.parent.document.querySelectorAll('input[type=text]');" " for (var i = 0; i < inputs.length; i++) {" @@ -46,8 +45,17 @@ def render_persistence_bridge() -> None: " }" " }" " }" - " setInput('persist_wl', stored_wl);" - " if (stored_tk) setInput('persist_tk', stored_tk);" + " if (d.dataset.loaded === '1') {" + " localStorage.setItem('prism_watchlist', d.dataset.wl);" + " if (d.dataset.tk) localStorage.setItem('prism_ticker', d.dataset.tk);" + " else localStorage.removeItem('prism_ticker');" + " } else {" + " var stored_wl = localStorage.getItem('prism_watchlist');" + " var stored_tk = localStorage.getItem('prism_ticker') || '';" + " if (!stored_wl) return;" + " setInput('persist_wl', stored_wl);" + " if (stored_tk) setInput('persist_tk', stored_tk);" + " }" "})();" "</script>" ) |
