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"
+ )