diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c41f54..1ae400f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Hermes Web UI -- Changelog +## [v0.50.48] fix: toast when model is switched during active session (#419) + +Synthesized from PRs #516 (armorbreak001), #517 and #518 (cloudyun888). + +When a user switches the model via the model picker while a session already +has messages, a 3-second toast now reads: "Model change takes effect in +your next conversation." This avoids the confusing situation where the +dropdown shows the new model but the current conversation continues with +the original one. + +The toast fires from `modelSelect.onchange` in `static/boot.js`, after the +existing provider-mismatch warning. It checks `S.messages.length > 0` (the +reliable in-memory array, always initialized by `loadSession`). The +`showToast` call is guarded with `typeof` for safety during boot. + +Key differences from submitted PRs: placement in boot.js onchange (covers +all selection paths including chip dropdown, since `selectModelFromDropdown` +calls `sel.onchange`), and uses `S.messages` not `S.session.messages`. + +4 new tests in `tests/test_provider_mismatch.py::TestModelSwitchToast`. + +Total tests: 1272 (was 1268) + + ## [v0.50.47] fix/feat: batch fixes — root workspace, custom providers, cron cache, system theme Synthesized from PRs #506, #507, #508, #509, #510, #514, #515, #519, #521. diff --git a/static/boot.js b/static/boot.js index 54fc472..771cd47 100644 --- a/static/boot.js +++ b/static/boot.js @@ -420,6 +420,10 @@ $('modelSelect').onchange=async()=>{ const warn=_checkProviderMismatch(selectedModel); if(warn&&typeof showToast==='function') showToast(warn,4000); } + // Notify user that model changes only take effect in the next conversation (#419) + if(S.messages && S.messages.length > 0 && typeof showToast==='function'){ + showToast('Model change takes effect in your next conversation', 3000); + } }; $('msg').addEventListener('input',()=>{ autoResize(); diff --git a/static/index.html b/static/index.html index 0ba9065..a1dd8cf 100644 --- a/static/index.html +++ b/static/index.html @@ -552,7 +552,7 @@
System
Instance version and access controls.
- v0.50.47 + v0.50.48
diff --git a/tests/test_provider_mismatch.py b/tests/test_provider_mismatch.py index fb72b94..9c81e91 100644 --- a/tests/test_provider_mismatch.py +++ b/tests/test_provider_mismatch.py @@ -264,3 +264,50 @@ def test_api_models_includes_active_provider(): "/api/models response missing 'active_provider' field — " "frontend needs this to detect provider mismatches" ) + + +# ── Model switch toast (#419) ───────────────────────────────────────────────── + +class TestModelSwitchToast: + """Toast appears when user switches model during an active session.""" + + def test_toast_in_model_select_onchange(self): + """modelSelect.onchange must show a toast when S.messages is non-empty.""" + src = _read("static/boot.js") + # Find the onchange block + idx = src.find("modelSelect').onchange") + assert idx != -1, "modelSelect.onchange not found in boot.js" + block = src[idx:idx + 1100] + assert "Model change takes effect in your next conversation" in block, ( + "modelSelect.onchange must show a toast when switching model mid-session" + ) + + def test_toast_guards_on_messages_length(self): + """Toast must only fire when there are existing messages (active session).""" + src = _read("static/boot.js") + idx = src.find("Model change takes effect in your next conversation") + assert idx != -1 + # Look back 200 chars for the S.messages guard + surrounding = src[max(0, idx - 200):idx + 50] + assert "S.messages" in surrounding and ".length" in surrounding, ( + "Model switch toast must be gated on S.messages.length > 0" + ) + + def test_toast_uses_show_toast_not_alert(self): + """Toast must use showToast(), not alert().""" + src = _read("static/boot.js") + idx = src.find("Model change takes effect in your next conversation") + assert idx != -1 + surrounding = src[max(0, idx - 50):idx + 100] + assert "showToast" in surrounding, "Must use showToast() not alert()" + assert "alert(" not in surrounding, "Must not use alert()" + + def test_toast_has_typeof_showtoast_guard(self): + """Toast call must guard typeof showToast to be safe during boot.""" + src = _read("static/boot.js") + idx = src.find("Model change takes effect in your next conversation") + assert idx != -1 + surrounding = src[max(0, idx - 100):idx + 50] + assert "typeof showToast" in surrounding, ( + "showToast call must be guarded with typeof check" + )