mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-16 20:50:18 +00:00
stage #4540 + CHANGELOG
This commit is contained in:
+3
-3
@@ -3,13 +3,13 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.51.536] — 2026-06-20 — Release SU (transparent-stream tool-call rows persist after settle)
|
||||
## [v0.51.537] — 2026-06-20 — Release SV (queued card clears on session switch)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Transparent-stream tool-call rows no longer vanish on turn settle (#4539).** The settled-node cleanup sweep in `renderMessages` was removing `.tool-card-row` elements regardless of type; it now excludes rows with `data-event-type="tool"` (the transparent-stream rows) so they survive settlement. The burst-key assignment also pre-scans burst IDs into a `Set` before the tool-card loop (O(1) lookup instead of O(n) spread per card), and only uses a burst key when the matching assistant segment is present. Thanks @rodboev.
|
||||
- **The queued-turn card from one session no longer bleeds into another when switching sessions (#4533).** The card-clear logic was only called for the *active* session's drain; session switch had no explicit clear, so a queued card from session A could persist visually when loading session B. `_clearQueueCardDisplay` is now called during `loadSession` on the session being left. Thanks @rodboev.
|
||||
|
||||
## [v0.51.535] — 2026-06-20 — Release ST (open model picker stays stable during stream sync)
|
||||
## [v0.51.536] — 2026-06-20 — Release SU (transparent-stream tool-call rows persist after settle)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -915,6 +915,7 @@ async function loadSession(sid){
|
||||
// draft survives page refresh and syncs across clients.
|
||||
if (currentSid && currentSid !== sid) {
|
||||
if(typeof window._clearPendingSelections==='function') window._clearPendingSelections();
|
||||
if(typeof _clearQueueCardDisplay==='function') _clearQueueCardDisplay(currentSid);
|
||||
await _saveComposerDraftNow(currentSid, ($('msg') || {}).value || '', S.pendingFiles ? [...S.pendingFiles] : []);
|
||||
// The awaited draft save above yields the event loop. If another
|
||||
// loadSession() started for a different session while we were waiting
|
||||
|
||||
+26
-8
@@ -5414,6 +5414,29 @@ function setBusy(v){
|
||||
// normal user bubble in the chat — the chip is removed at drain time.
|
||||
const _queueRenderKeys={}; // per-session fingerprint to avoid redundant rebuilds
|
||||
const _queueCollapsed={}; // per-session: true when user explicitly collapsed the card
|
||||
let _queueRenderEpoch=0;
|
||||
function _clearQueueCardDisplay(sid){
|
||||
const card=document.getElementById('queueCard');
|
||||
const chips=document.getElementById('queueChips');
|
||||
if(sid) delete _queueRenderKeys[sid];
|
||||
if(card) card.classList.remove('visible');
|
||||
if(chips){
|
||||
const _chips=chips;
|
||||
const _card=card;
|
||||
const _sid=String(sid||'');
|
||||
const _epoch=_chips.getAttribute('data-queue-render-epoch')||'';
|
||||
setTimeout(()=>{
|
||||
if((_card&&!_card.classList.contains('visible'))||!_card){
|
||||
if((_chips.getAttribute('data-queue-render-sid')||'')===_sid&&(_chips.getAttribute('data-queue-render-epoch')||'')===_epoch){
|
||||
_chips.innerHTML='';
|
||||
}
|
||||
}
|
||||
},360);
|
||||
}
|
||||
const _msgsEl=document.getElementById('messages');
|
||||
if(_msgsEl) _msgsEl.classList.remove('queue-open');
|
||||
_updateQueuePill(sid,0);
|
||||
}
|
||||
|
||||
function _renderQueueChips(sid){
|
||||
const card=document.getElementById('queueCard');
|
||||
@@ -5425,6 +5448,8 @@ function _renderQueueChips(sid){
|
||||
// Skip re-render if user is actively editing inside the queue panel
|
||||
if(inner.contains(document.activeElement)&&document.activeElement!==inner) return;
|
||||
_queueRenderKeys[sid]=key;
|
||||
inner.setAttribute('data-queue-render-sid',sid);
|
||||
inner.setAttribute('data-queue-render-epoch',String(++_queueRenderEpoch));
|
||||
inner.innerHTML='';
|
||||
if(!q.length){
|
||||
card.classList.remove('visible');
|
||||
@@ -5663,14 +5688,7 @@ function updateQueueBadge(sessionId){
|
||||
// Only wipe global DOM if this is the currently active session
|
||||
const isActive=S.session&&sid===S.session.session_id;
|
||||
if(isActive){
|
||||
const card=document.getElementById('queueCard');
|
||||
const chips=document.getElementById('queueChips');
|
||||
if(card) card.classList.remove('visible');
|
||||
// Defer clear until after slide-out transition so content doesn't vanish mid-animation
|
||||
if(chips){const _chips=chips;const _card=card;setTimeout(()=>{if(!_card||!_card.classList.contains('visible'))_chips.innerHTML='';},360);}
|
||||
const _msgsEl=document.getElementById('messages');
|
||||
if(_msgsEl) _msgsEl.classList.remove('queue-open');
|
||||
_updateQueuePill(sid,0);
|
||||
_clearQueueCardDisplay(sid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,6 +534,59 @@ def test_session_scoped_message_queue_frontend_wiring(cleanup_test_sessions):
|
||||
assert "updateQueueBadge(sid);" in sessions_src
|
||||
|
||||
|
||||
def test_queue_card_cross_session_clear_called_before_draft_save(cleanup_test_sessions):
|
||||
"""R15c: switching away from one session to another should clear the old
|
||||
session's queue card before the async draft-save await, so stale DOM cannot
|
||||
survive into the destination session.
|
||||
"""
|
||||
src = (REPO_ROOT / "static/sessions.js").read_text()
|
||||
block_pattern = re.compile(
|
||||
r"if \(currentSid && currentSid !== sid\) \{\s*"
|
||||
r"if\(typeof window\._clearPendingSelections==='function'\) window\._clearPendingSelections\(\);\s*"
|
||||
r"if\(typeof _clearQueueCardDisplay==='function'\) _clearQueueCardDisplay\(currentSid\);\s*"
|
||||
r"await _saveComposerDraftNow\(currentSid",
|
||||
re.S,
|
||||
)
|
||||
assert block_pattern.search(src), (
|
||||
"cross-session loadSession path must clear queue card display via"
|
||||
" _clearQueueCardDisplay(currentSid) before awaiting _saveComposerDraftNow"
|
||||
)
|
||||
|
||||
|
||||
def test_queue_card_cross_session_helper_used_only_for_session_change(cleanup_test_sessions):
|
||||
"""R15c: _clearQueueCardDisplay(sid) must only fire on cross-session switches,
|
||||
not on same-session navigation/force-reload code paths.
|
||||
"""
|
||||
src = (REPO_ROOT / "static/sessions.js").read_text()
|
||||
load_start = src.find("async function loadSession(sid){")
|
||||
assert load_start >= 0
|
||||
load_end = src.find(" // Sync context usage indicator from session data", load_start)
|
||||
load_body = src[load_start:load_end]
|
||||
cross_start = load_body.find("if (currentSid && currentSid !== sid) {")
|
||||
cross_end = load_body.find("if (currentSid !== sid || forceReload) {", cross_start)
|
||||
assert cross_start >= 0 and cross_end >= 0
|
||||
assert "_clearQueueCardDisplay(currentSid);" in load_body[cross_start:cross_end], (
|
||||
"queue-card clear helper must be inside the cross-session branch"
|
||||
)
|
||||
same_session_idx = load_body.find("if(currentSid===sid && !forceReload && !_loadingSessionId) return;")
|
||||
assert same_session_idx >= 0
|
||||
assert same_session_idx < cross_start
|
||||
|
||||
|
||||
def test_queue_card_clear_helper_tracks_render_epoch(cleanup_test_sessions):
|
||||
"""R15d: the delayed queue clear must only wipe the chips from the render it
|
||||
was asked to dismiss, not a later render for the same shared queue DOM.
|
||||
"""
|
||||
src = (REPO_ROOT / "static/ui.js").read_text()
|
||||
assert "let _queueRenderEpoch=0;" in src
|
||||
assert "if(sid) delete _queueRenderKeys[sid];" in src
|
||||
assert "inner.setAttribute('data-queue-render-sid',sid);" in src
|
||||
assert "inner.setAttribute('data-queue-render-epoch',String(++_queueRenderEpoch));" in src
|
||||
assert "const _epoch=_chips.getAttribute('data-queue-render-epoch')||'';" in src
|
||||
assert "(_chips.getAttribute('data-queue-render-sid')||'')===_sid" in src
|
||||
assert "(_chips.getAttribute('data-queue-render-epoch')||'')===_epoch" in src
|
||||
|
||||
|
||||
def test_chat_start_persists_pending_turn_metadata_for_reload_recovery(cleanup_test_sessions):
|
||||
"""R15c: chat/start must expose enough pending-turn metadata for a reload to
|
||||
rebuild the in-flight conversation instead of showing a blank session.
|
||||
|
||||
Reference in New Issue
Block a user