From a4b2848172119232f6533ad7ab73f9091dc231b5 Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:14:21 +0200 Subject: [PATCH] Fix WebUI message replay ordering Treat full log snapshots as authoritative replays by clearing the message DOM when the snapshot starts at log no 0, then render logs in backend order. This prevents streamed responses from staying above earlier welcome/user messages after sync races while keeping incremental updates patch-based. --- tests/test_webui_message_ordering_static.py | 24 +++++++++++++++++++++ webui/index.js | 4 ++++ webui/js/AGENTS.md | 1 + webui/js/messages.js | 3 +++ 4 files changed, 32 insertions(+) create mode 100644 tests/test_webui_message_ordering_static.py diff --git a/tests/test_webui_message_ordering_static.py b/tests/test_webui_message_ordering_static.py new file mode 100644 index 000000000..7c9fae509 --- /dev/null +++ b/tests/test_webui_message_ordering_static.py @@ -0,0 +1,24 @@ +from pathlib import Path + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] + + +def read(*parts: str) -> str: + return (PROJECT_ROOT / Path(*parts)).read_text(encoding="utf-8") + + +def test_full_log_replays_replace_existing_message_dom(): + index_js = read("webui", "index.js") + messages_js = read("webui", "js", "messages.js") + + assert "snapshot.logs?.[0]?.no === 0" in index_js + assert 'chatHistoryEl.innerHTML = "";' in index_js + assert "messages.sort((a, b)" in messages_js + + +def test_message_ordering_fix_does_not_add_renderer_cache_state(): + messages_js = read("webui", "js", "messages.js") + + assert "_messageCacheByNo" not in messages_js + assert "resetMessageRenderState" not in messages_js diff --git a/webui/index.js b/webui/index.js index de5b64bc3..715166ee1 100644 --- a/webui/index.js +++ b/webui/index.js @@ -348,6 +348,10 @@ export async function applySnapshot(snapshot, options = {}) { if (lastLogVersion != snapshot.log_version) { updated = true; + if (snapshot.logs?.[0]?.no === 0) { + const chatHistoryEl = document.getElementById("chat-history"); + if (chatHistoryEl) chatHistoryEl.innerHTML = ""; + } await setMessages(snapshot.logs); afterMessagesUpdate(snapshot.logs); } diff --git a/webui/js/AGENTS.md b/webui/js/AGENTS.md index d1b28e95c..53fc1fb3d 100644 --- a/webui/js/AGENTS.md +++ b/webui/js/AGENTS.md @@ -39,6 +39,7 @@ - Frontend extension hooks such as `confirm_dialog_after_render` and `get_tool_message_handler` must preserve their mutable context contracts. - Sanitize or safely render user/model-provided HTML and markdown. - Do not expose secrets in localStorage, console logs, URLs, or WebSocket payloads. +- Full message snapshots that start at backend log `no` 0 must replace the current message DOM before rendering; incremental snapshots should keep patching existing messages. ## Work Guidance diff --git a/webui/js/messages.js b/webui/js/messages.js index 6ed61338e..92d42e15d 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -137,6 +137,9 @@ export async function getMessageHandler(type) { // entrypoint called from poll/WS communication, this is how all messages are rendered and updated // input is raw log format export async function setMessages(messages) { + messages = Array.isArray(messages) ? [...messages].filter(Boolean) : []; + messages.sort((a, b) => (a.no ?? Number.MAX_SAFE_INTEGER) - (b.no ?? Number.MAX_SAFE_INTEGER)); + const context = { messages, history: getChatHistoryEl(),