From a8ef99b83088e024962f99c805e448f5419a461c Mon Sep 17 00:00:00 2001 From: Jaret Bottoms <25535214+jbbottoms@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:07:51 -0500 Subject: [PATCH] =?UTF-8?q?no-mistakes(review):=20Reset=20cron=20unread=20?= =?UTF-8?q?state=20on=20alternate=20profile=20switches=20=F0=9F=94=A7?= =?UTF-8?q?=F0=9F=9F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/sessions.js | 3 ++ ...est_issue5960_cron_unread_profile_scope.py | 40 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/static/sessions.js b/static/sessions.js index 5863cf9f..4dd28e8c 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -1438,6 +1438,9 @@ async function _switchProfileForSessionLoad(profile){ const data=await api('/api/profile/switch',{method:'POST',body:JSON.stringify({name}),timeoutToast:false}); S.activeProfile=data.active||name; S.activeProfileIsDefault=!!data.is_default; + if(typeof _resetCronUnreadForProfileSwitch==='function'){ + _resetCronUnreadForProfileSwitch(); + } if(typeof _clearPersistedModelState==='function') _clearPersistedModelState(); else localStorage.removeItem('hermes-webui-model'); if(data.default_model) window._defaultModel=data.default_model; diff --git a/tests/test_issue5960_cron_unread_profile_scope.py b/tests/test_issue5960_cron_unread_profile_scope.py index e21f26c0..7bbbbae4 100644 --- a/tests/test_issue5960_cron_unread_profile_scope.py +++ b/tests/test_issue5960_cron_unread_profile_scope.py @@ -12,12 +12,15 @@ import pytest ROOT = Path(__file__).resolve().parents[1] PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8") +SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") ROUTES_PY = (ROOT / "api" / "routes.py").read_text(encoding="utf-8") NODE = shutil.which("node") def _extract_function(source: str, name: str) -> str: start = source.index(f"function {name}(") + if source[max(0, start - 6) : start] == "async ": + start -= 6 brace = source.index("{", start) depth = 1 pos = brace + 1 @@ -61,6 +64,43 @@ def test_successful_profile_switch_resets_unread_cron_state(): assert "updateCronBadge();" in reset_body +@pytest.mark.skipif(NODE is None, reason="node not on PATH") +def test_session_load_profile_switch_resets_unread_cron_state(): + switch = _extract_function(SESSIONS_JS, "_switchProfileForSessionLoad") + reset = _extract_function(PANELS_JS, "_resetCronUnreadForProfileSwitch") + script = f""" +let _cronPollSince=10; +let _cronUnreadCount=1; +let _cronPollGeneration=0; +const _cronNewJobIds=new Set(['old-profile-job']); +global.S={{activeProfile:'default'}}; +global.api=async()=>({{active:'alternate',is_default:false}}); +global.localStorage={{removeItem(){{}}}}; +global.updateCronBadge=()=>{{ _cronUnreadCount=_cronNewJobIds.size; }}; +{reset} +{switch} +(async()=>{{ + await _switchProfileForSessionLoad('alternate'); + process.stdout.write(JSON.stringify({{ + profile:S.activeProfile, + unread:Array.from(_cronNewJobIds), + count:_cronUnreadCount, + generation:_cronPollGeneration, + }})); +}})().catch(error=>{{ console.error(error); process.exit(1); }}); +""" + result = subprocess.run( + [NODE, "-e", script], check=True, capture_output=True, text=True, timeout=30 + ) + state = json.loads(result.stdout) + assert state == { + "profile": "alternate", + "unread": [], + "count": 0, + "generation": 1, + } + + @pytest.mark.skipif(NODE is None, reason="node not on PATH") def test_poll_started_before_switch_cannot_recreate_unread_state(): polling = _extract_function(PANELS_JS, "startCronPolling")