mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 03:00:23 +00:00
fix(session): keep conversation actions menu clickable
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Keep the sidebar conversation actions menu open while session-list refreshes, stream updates, or panel-resync repairs arrive, so the three-dot menu beside chat titles remains usable while the user is interacting with it.
|
||||
|
||||
## [v0.51.107] — 2026-05-21 — Release CE (stage-400 — 8-PR batch — pinned-sessions-limit getter rename + uploaded-file user-turn dedupe + active-run repair guard + incremental KaTeX streaming + profile default model on fresh boot + French locale completion + update-check error surfacing + release-update apply path)
|
||||
|
||||
@@ -17,6 +20,7 @@
|
||||
- **PR #2717** by @ai-ag2026 — Surface update-check fetch errors in the UI instead of failing silently. The background `api/updates/check` request previously swallowed network failures, so an offline / blocked-CDN scenario showed no indication that the version banner couldn't render. Now the failure is logged and exposed to the System panel's update-status card.
|
||||
- **PR #2719** by @ai-ag2026 — Apply release-update target correctly when the user clicks "Check for updates" after a prior dismissal: clears the `sessionStorage` check-once stamp and forces banner re-evaluation. The prior path silently no-op'd because the once-per-tab guard fired before the explicit user click could re-trigger the fetch.
|
||||
|
||||
|
||||
## [v0.51.106] — 2026-05-21 — Release CD (stage-399 — 3-PR batch — restamped state.db replay dedupe + context_messages dedupe so agent doesn't see duplicates + empty _partial bloat fix)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -2970,6 +2970,11 @@ function _resyncSessionVirtualWindowAfterRender(list, expectedScrollTop, virtual
|
||||
function renderSessionListFromCache(){
|
||||
// Don't re-render while user is actively renaming a session (would destroy the input)
|
||||
if(_renamingSid) return;
|
||||
// Keep the per-conversation actions menu stable while the user is trying to
|
||||
// click it. Sidebar syncs, stream/unread updates, and panel-resync repairs can
|
||||
// all call this while the fixed-position menu is open; rebuilding the row DOM
|
||||
// here removes the anchor and makes the menu feel unclickable.
|
||||
if(_sessionActionMenu) return;
|
||||
closeSessionActionMenu();
|
||||
// Purge stale INFLIGHT entries for sessions the server confirms are NOT
|
||||
// streaming. This runs on every list refresh to prevent memory leaks from
|
||||
|
||||
@@ -53,6 +53,7 @@ def test_pin_limit_setting_is_exposed_and_wired_through_ui():
|
||||
assert "settings.pinned_sessions_limit" in PANELS_JS
|
||||
assert "window._pinnedSessionsLimit=parseInt(s.pinned_sessions_limit||3,10)||3" in BOOT_JS
|
||||
assert "function _getPinnedSessionsLimit()" in SESSIONS_JS
|
||||
assert "function _pinnedSessionsLimit()" not in SESSIONS_JS
|
||||
assert "_pinnedSessionCount()>=_getPinnedSessionsLimit()" in SESSIONS_JS
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ def test_session_pin_cap_has_backend_and_frontend_guards():
|
||||
|
||||
assert 'function _pinnedSessionCount()' in SESSIONS_JS
|
||||
assert 'function _getPinnedSessionsLimit()' in SESSIONS_JS
|
||||
assert 'function _pinnedSessionsLimit()' not in SESSIONS_JS
|
||||
assert 'const pinLimitReached=!session.pinned&&_pinnedSessionCount()>=_getPinnedSessionsLimit();' in SESSIONS_JS
|
||||
assert 'Only ${limit} conversations can be pinned' in SESSIONS_JS
|
||||
assert ".session-action-opt.is-disabled{opacity:.55;cursor:not-allowed;}" in STYLE_CSS
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Regression checks for per-conversation action menu click stability."""
|
||||
from pathlib import Path
|
||||
|
||||
SESSIONS_JS = (Path(__file__).resolve().parent.parent / "static" / "sessions.js").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _function_block(src: str, name: str) -> str:
|
||||
marker = f"function {name}"
|
||||
start = src.find(marker)
|
||||
assert start != -1, f"{name} not found"
|
||||
brace = src.find("{", start)
|
||||
assert brace != -1, f"{name} body not found"
|
||||
depth = 1
|
||||
i = brace + 1
|
||||
while i < len(src) and depth:
|
||||
if src[i] == "{":
|
||||
depth += 1
|
||||
elif src[i] == "}":
|
||||
depth -= 1
|
||||
i += 1
|
||||
assert depth == 0, f"{name} body did not close"
|
||||
return src[start:i]
|
||||
|
||||
|
||||
def test_session_list_refresh_does_not_close_open_conversation_actions():
|
||||
"""Sidebar refreshes must not eat the three-dot menu before users can click it."""
|
||||
body = _function_block(SESSIONS_JS, "renderSessionListFromCache")
|
||||
|
||||
assert "if(_renamingSid) return;" in body
|
||||
assert "if(_sessionActionMenu) return;" in body
|
||||
assert body.index("if(_sessionActionMenu) return;") < body.index("closeSessionActionMenu();")
|
||||
Reference in New Issue
Block a user