From 7686385844c89b362fffff3cee0494227b010475 Mon Sep 17 00:00:00 2001 From: 2002yy <15135142681@163.com> Date: Sun, 17 May 2026 16:24:40 +0800 Subject: [PATCH] fix: sidebar full-page rerun for global settings, add _settings_changed pure fn - Added _rerun_app() helper using st.rerun() (no scope) for global ops - Added _settings_changed() pure function to detect setting changes - apply_settings now clears stale current_route when any global setting changes - All 6 global-affecting sidebar operations (apply settings, runtime flags, memory flags, view unread, clear unread, force refresh) now use full-page rerun instead of fragment-scoped rerun - 12 new tests covering _settings_changed, source checks for rerun strategy, and current_route clearing Co-Authored-By: Claude Opus 4.7 --- src/ui/sidebar.py | 51 +++++++++++-- tests/test_sidebar_global_rerun.py | 114 +++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 tests/test_sidebar_global_rerun.py diff --git a/src/ui/sidebar.py b/src/ui/sidebar.py index 9cb21b6..ea1644f 100644 --- a/src/ui/sidebar.py +++ b/src/ui/sidebar.py @@ -39,6 +39,34 @@ ) +def _rerun_app(): + st.rerun() + + +def _settings_changed( + entry_mode_new: str, + current_role_new: str, + current_mode_new: str, + model_profile_new: str, + performance_mode_new: str, + atmosphere_new: str, + entry_mode_old: str, + current_role_old: str, + current_mode_old: str, + model_profile_old: str, + performance_mode_old: str, + atmosphere_old: str, +) -> bool: + return ( + entry_mode_new != entry_mode_old + or current_role_new != current_role_old + or current_mode_new != current_mode_old + or model_profile_new != model_profile_old + or performance_mode_new != performance_mode_old + or atmosphere_new != atmosphere_old + ) + + def _switch_to_wechat_entry(unread_content: str, runtime_modes, session_state) -> None: session_state.wechat_messages = unread_content if runtime_modes.entry_mode != "wechat": @@ -155,6 +183,14 @@ def render_sidebar(): apply_settings = st.form_submit_button("应用设置", use_container_width=True) if apply_settings: + anything_changed = _settings_changed( + entry_mode_new, current_role_new, current_mode_new, model_profile_new, + performance_mode_new, atmosphere_new, + runtime_modes.entry_mode, st.session_state.current_role, + st.session_state.current_mode, st.session_state.model_profile, + runtime_modes.performance_mode, st.session_state.interaction_mode, + ) + if entry_mode_new != runtime_modes.entry_mode: update_entry_mode(entry_mode_new) runtime_modes.entry_mode = entry_mode_new @@ -174,8 +210,11 @@ def render_sidebar(): update_interaction_mode(atmosphere_new) set_interaction_mode(st.session_state.session_id, atmosphere_new) + if anything_changed: + st.session_state.current_route = {} + st.session_state.sidebar_notice = "设置已应用" - st.rerun(scope="fragment") + _rerun_app() st.markdown('', unsafe_allow_html=True) @@ -207,7 +246,7 @@ def render_sidebar(): update_safe_mode(safe_new) runtime_modes.safe_mode = safe_new st.session_state.sidebar_notice = "状态开关已应用" - st.rerun(scope="fragment") + _rerun_app() st.markdown('', unsafe_allow_html=True) @@ -241,7 +280,7 @@ def render_sidebar(): "enabled" if capture_enabled_new else "disabled", ) st.session_state.sidebar_notice = "记忆设置已应用" - st.rerun(scope="fragment") + _rerun_app() st.markdown('', unsafe_allow_html=True) @@ -276,14 +315,14 @@ def render_sidebar(): has_unread = unread_content and "暂无未读" not in unread_content if has_unread and st.button("查看未读消息", use_container_width=True): _switch_to_wechat_entry(unread_content, runtime_modes, st.session_state) - st.rerun(scope="fragment") + _rerun_app() if st.button("清空未读消息", use_container_width=True): clear_wechat_unread() set_wechat_unread_cleared(st.session_state.session_id) st.session_state.wechat_messages = None st.session_state.sidebar_notice = "未读消息已清空" - st.rerun(scope="fragment") + _rerun_app() cols = st.columns(2) with cols[0]: @@ -297,7 +336,7 @@ def render_sidebar(): st.session_state.wechat_messages = None st.session_state.health_report = health_report(force_refresh=True) st.session_state.sidebar_notice = "配置、记忆与健康状态已刷新" - st.rerun(scope="fragment") + _rerun_app() if st.session_state.get("health_report"): with st.expander("健康报告", expanded=False): diff --git a/tests/test_sidebar_global_rerun.py b/tests/test_sidebar_global_rerun.py new file mode 100644 index 0000000..58de3d2 --- /dev/null +++ b/tests/test_sidebar_global_rerun.py @@ -0,0 +1,114 @@ +"""Tests for sidebar global rerun behavior. + +Verifies that global-affecting sidebar operations use full-page rerun +instead of fragment-scoped rerun, and that settings changes clear stale routes. +""" + +from pathlib import Path + + +def test_sidebar_has_rerun_app(): + text = Path("src/ui/sidebar.py").read_text(encoding="utf-8") + assert "def _rerun_app():" in text + assert "st.rerun()" in text + + +def test_sidebar_has_settings_changed_pure_function(): + text = Path("src/ui/sidebar.py").read_text(encoding="utf-8") + assert "def _settings_changed(" in text + + +def test_no_fragment_rerun_in_sidebar(): + """All global-affecting operations must use _rerun_app, not fragment rerun.""" + text = Path("src/ui/sidebar.py").read_text(encoding="utf-8") + assert 'st.rerun(scope="fragment")' not in text + + +def test_apply_settings_uses_rerun_app(): + text = Path("src/ui/sidebar.py").read_text(encoding="utf-8") + lines = text.splitlines() + apply_idx = None + for i, line in enumerate(lines): + if "if apply_settings:" in line.strip(): + apply_idx = i + break + assert apply_idx is not None, "apply_settings branch not found" + found = any("_rerun_app()" in lines[j] for j in range(apply_idx, apply_idx + 50)) + assert found, "apply_settings block does not call _rerun_app()" + + +def test_apply_settings_clears_current_route(): + text = Path("src/ui/sidebar.py").read_text(encoding="utf-8") + lines = text.splitlines() + apply_idx = None + for i, line in enumerate(lines): + if "if apply_settings:" in line.strip(): + apply_idx = i + break + assert apply_idx is not None + block = "\n".join(lines[apply_idx : apply_idx + 50]) + assert "anything_changed" in block + assert "current_route" in block + + +def test_settings_changed_no_change(): + from src.ui.sidebar import _settings_changed + + assert not _settings_changed( + "wechat", "march7", "auto", "flash", "standard", "standard", + "wechat", "march7", "auto", "flash", "standard", "standard", + ) + + +def test_settings_changed_entry_mode(): + from src.ui.sidebar import _settings_changed + + assert _settings_changed( + "single", "march7", "auto", "flash", "standard", "standard", + "wechat", "march7", "auto", "flash", "standard", "standard", + ) + + +def test_settings_changed_role(): + from src.ui.sidebar import _settings_changed + + assert _settings_changed( + "wechat", "keqing", "auto", "flash", "standard", "standard", + "wechat", "march7", "auto", "flash", "standard", "standard", + ) + + +def test_settings_changed_mode(): + from src.ui.sidebar import _settings_changed + + assert _settings_changed( + "wechat", "march7", "论文", "flash", "standard", "standard", + "wechat", "march7", "auto", "flash", "standard", "standard", + ) + + +def test_settings_changed_model(): + from src.ui.sidebar import _settings_changed + + assert _settings_changed( + "wechat", "march7", "auto", "pro", "standard", "standard", + "wechat", "march7", "auto", "flash", "standard", "standard", + ) + + +def test_settings_changed_performance(): + from src.ui.sidebar import _settings_changed + + assert _settings_changed( + "wechat", "march7", "auto", "flash", "deep", "standard", + "wechat", "march7", "auto", "flash", "standard", "standard", + ) + + +def test_settings_changed_atmosphere(): + from src.ui.sidebar import _settings_changed + + assert _settings_changed( + "wechat", "march7", "auto", "flash", "standard", "warm", + "wechat", "march7", "auto", "flash", "standard", "standard", + )