Fix plugin reload toast behavior
Persist the frontend-extension notification as read before reloading so it does not reappear after refresh. Align plugin and updater toast actions with the shared small spacing token.
Alessandro committed
Jul 12, 2026 at 17:26 UTC
4b5077c493cb0c698ec498cec73e6ed89de14d4f
6 files changed
+21
-3
helpers/plugins.py
+1
-1
@@ -840,7 +840,7 @@ def send_frontend_reload_notification(plugin_names: list[str] | None = None):
840
type=notification.NotificationType.INFO,
841
priority=notification.NotificationPriority.NORMAL,
842
title="Plugins with frontend extensions updated, page reload recommended",
843
- message="""<button type="button" class="button confirm" onclick="window.location.reload()"><span class="icon material-symbols-outlined">refresh</span>Reload page</button>""",
843
+ message="""<div class="toast-action-row"><button type="button" class="button confirm" @click.stop="$store.notificationStore.dismissToastAndReload(toast.toastId)"><span class="icon material-symbols-outlined">refresh</span>Reload page</button></div>""",
844
detail="",
845
display_time=0,
846
group="plugins_changed",
helpers/plugins.py.dox.md
+1
-1
@@ -40,7 +40,7 @@
40
- `find_plugin_asset(plugin_name: str, *subpaths, project_name=..., agent_profile=...)`
41
- `find_plugin_assets(*subpaths, plugin_name: str=..., project_name: str=..., agent_profile: str=..., only_first: bool=...) -> list[PluginAssetFile]`
42
- `determine_plugin_asset_path(plugin_name: str, project_name: str, agent_profile: str, *subpaths)`
43
-- `send_frontend_reload_notification(plugin_names: list[str] | None=...)`: If the plugin changed has webui extensions, show a persistent reload notification
43
+- `send_frontend_reload_notification(plugin_names: list[str] | None=...)`: If the plugin changed has webui extensions, show a persistent reload notification that marks itself read before refreshing
44
- `call_plugin_hook(plugin_name: str, hook_name: str, default: Any=..., *args, **kwargs)`
45
- `_apply_defaults_from_env(plugin_name: str, config: dict[str, Any])`
46
- Notable constants/configuration names: `_META_TARGET_RE`, `META_FILE_NAME`, `CONFIG_FILE_NAME`, `CONFIG_DEFAULT_FILE_NAME`, `DISABLED_FILE_NAME`, `ENABLED_FILE_NAME`, `TOGGLE_FILE_PATTERN`, `HOOKS_SCRIPT`, `HOOKS_CACHE_AREA`, `PLUGINS_LIST_CACHE_AREA`, `ENABLED_PLUGINS_LIST_CACHE_AREA`, `ENABLED_PLUGINS_PATHS_CACHE_AREA`.
tests/test_download_toast_regressions.py
+7
@@ -101,13 +101,20 @@ def test_notification_store_supports_persistent_grouped_toasts():
101
assert "if display_time < 0:" in api
102
assert "if display_time <= 0:" not in api
103
assert 'id="plugins_frontend_reload",' in plugins
104
+ assert "$store.notificationStore.dismissToastAndReload(toast.toastId)" in plugins
105
+ assert "onclick=\"window.location.reload()\"" not in plugins
106
assert 'id=notif.get("id", "update_check_available"),' in update_check
107
assert "display_time=0," in plugins
108
assert "display_time=0," in update_check
109
+ assert 'class="toast-action-row"' in plugins
110
assert 'class="toast-action-row"' in update_check
111
assert 'class="button confirm"' in update_check
112
assert "$store.notificationStore.dismissToast(toast.toastId)" in update_check
113
assert ".toast-action-row" in toast_stack
114
+ assert "margin-top: var(--spacing-sm);" in toast_stack
115
+ assert "async dismissToastAndReload(toastId)" in store
116
+ assert 'await API.callJsonApi("notifications_mark_read"' in store
117
+ assert "if (response?.success) window.location.reload();" in store
118
119
120
def test_backup_zip_downloads_emit_grouped_preparing_and_downloading_toasts():
webui/components/notifications/AGENTS.md
+1
@@ -16,6 +16,7 @@
16
- Use the notification system for user-facing success, warning, info, and error feedback.
17
- Keep public helper names stable for core and plugin callers.
18
- Keep the toast stack visible above normal and legacy modal overlays while staying below confirmation dialogs.
19
+- Reload actions must persist their notification as read before refreshing the page.
20
- Avoid exposing secrets or raw auth payloads in notification text.
21
22
## Work Guidance
webui/components/notifications/notification-store.js
+10
@@ -200,6 +200,16 @@ const model = {
200
this.removeFromToastStack(toastId, true);
201
},
202
203
+ async dismissToastAndReload(toastId) {
204
+ const toast = this.toastStack.find((item) => item.toastId === toastId);
205
+ if (!toast?.id) return;
206
+
207
+ const response = await API.callJsonApi("notifications_mark_read", {
208
+ notification_ids: [toast.id],
209
+ });
210
+ if (response?.success) window.location.reload();
211
+ },
212
+
213
async afterToastRemoved(toast, removedByUser = false) {
214
// if the toast is closed by the user OR timed out with normal priority, mark it as read
215
if (removedByUser || toast.priority <= NotificationPriority.NORMAL) {
webui/components/notifications/notification-toast-stack.html
+1
-1
@@ -151,7 +151,7 @@
151
}
152
153
.toast-action-row {
154
- margin-top: 10px;
154
+ margin-top: var(--spacing-sm);
155
}
156
157
.toast-timestamp {