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(),