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.
This commit is contained in:
Alessandro 2026-06-26 14:14:21 +02:00
parent 7160747607
commit a4b2848172
4 changed files with 32 additions and 0 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

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