From 84990f4a69f58260c4021556e2386897f8954eb2 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sun, 14 Jun 2026 14:54:15 -0400 Subject: [PATCH 1/3] fix(sidebar): preserve GitHub issue numbers in session titles (#4154) --- static/sessions.js | 4 +- tests/test_issue4154_sidebar_issue_numbers.py | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 tests/test_issue4154_sidebar_issue_numbers.py diff --git a/static/sessions.js b/static/sessions.js index 6725ebde7..e6074f6b3 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -4726,7 +4726,7 @@ function _sessionTitleIsDefaultWebUI(rawTitle){ function _sessionTitleTags(rawTitle){ if(_sessionTitleIsDefaultWebUI(rawTitle)) return []; - return String(rawTitle||'').match(/#[\w-]+/g)||[]; + return String(rawTitle||'').match(/#(?!\d+\b)[\w-]+/g)||[]; } function _activeSessionIdForSidebar(){ @@ -5427,7 +5427,7 @@ function renderSessionListFromCache(){ if(isActive&&S.session&&S.session._flash)delete S.session._flash; const rawTitle=_sessionDisplayTitle(s); const tags=_sessionTitleTags(rawTitle); - let cleanTitle=tags.length?rawTitle.replace(/#[\w-]+/g,'').trim():rawTitle; + let cleanTitle=tags.length?rawTitle.replace(/#(?!\d+\b)[\w-]+/g,'').trim():rawTitle; // Guard: system prompt content must never surface as a visible session title if(cleanTitle.startsWith('[SYSTEM:')){ cleanTitle='Session'; diff --git a/tests/test_issue4154_sidebar_issue_numbers.py b/tests/test_issue4154_sidebar_issue_numbers.py new file mode 100644 index 000000000..c1d52f916 --- /dev/null +++ b/tests/test_issue4154_sidebar_issue_numbers.py @@ -0,0 +1,67 @@ +"""Regression tests for #4154: sidebar strips GitHub issue numbers from titles. + +The _sessionTitleTags regex must NOT treat purely numeric #NNNN patterns as +tags. Only known attention/session-control tags like #approval, #clarify, +#attention should be extracted. +""" + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SESSIONS_JS = ROOT / "static" / "sessions.js" + + +def _read_sessions_js(): + return SESSIONS_JS.read_text(encoding="utf-8") + + +def test_sessionTitleTags_function_exists(): + """The _sessionTitleTags function must be present in sessions.js.""" + src = _read_sessions_js() + assert "function _sessionTitleTags" in src + + +def test_regex_excludes_numeric_issue_numbers(): + """The regex in _sessionTitleTags must use a negative lookahead to skip + purely numeric #NNNN patterns (GitHub issue references).""" + src = _read_sessions_js() + # The fix replaces /#[\w-]+/g with /#(?!\d+\b)[\w-]+/g + assert r"#(?!\d+\b)[\w-]+" in src, ( + "Expected negative lookahead regex /#(??!\\d+\\b)[\\w-]+/ in _sessionTitleTags" + ) + # The old broad pattern must NOT be the only pattern present + # (the negative lookahead variant must exist) + + +def test_old_broad_regex_not_still_present(): + r"""The old /#[\w-]+/g pattern (without negative lookahead) must not appear + in _sessionTitleTags or _renderOneSession tag-stripping code.""" + src = _read_sessions_js() + # Find _sessionTitleTags function body + match = re.search( + r"function _sessionTitleTags\(rawTitle\)\{(.*?)\n\}", + src, + re.DOTALL, + ) + assert match, "Could not locate _sessionTitleTags function body" + body = match.group(1) + # The function body must not contain the old broad regex + assert "/#[\\w-]+/g" not in body.replace(" ", ""), ( + "_sessionTitleTags still uses the old broad /#[\\w-]+/g regex" + ) + + +def test_regex_applied_in_renderOneSession_too(): + """The tag-stripping regex in _renderOneSession must also use the + negative lookahead so visible titles keep issue numbers.""" + src = _read_sessions_js() + # Find the cleanTitle line + assert r"#(?!\d+\b)[\w-]+" in src + # Confirm it appears at least twice (once in _sessionTitleTags, once in + # _renderOneSession) + count = src.count(r"#(?!\d+\b)[\w-]+") + assert count >= 2, ( + f"Expected negative lookahead regex in at least 2 locations " + f"(_sessionTitleTags + _renderOneSession), found {count}" + ) From 8c105bd31d12d918bd068bec72ab14e18f91e566 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sun, 14 Jun 2026 14:54:19 -0400 Subject: [PATCH 2/3] fix(sessions): guard external refresh poll to skip non-external sessions (#3916) --- static/sessions.js | 1 + tests/test_issue3916_external_refresh_poll.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/test_issue3916_external_refresh_poll.py diff --git a/static/sessions.js b/static/sessions.js index 6725ebde7..eead66d58 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -3650,6 +3650,7 @@ async function refreshActiveSessionIfExternallyUpdated(reason){ if(_activeSessionExternalRefreshInFlight) return; if(!S.session || !S.session.session_id) return; if(S.busy || S.activeStreamId) return; + if(!_isExternalSession(S.session)) return; // Cooldown: don't force-reload immediately after streaming ends — the // "done" event already delivered the final messages. Reloading here would // clear S.toolCalls and lose Activity. diff --git a/tests/test_issue3916_external_refresh_poll.py b/tests/test_issue3916_external_refresh_poll.py new file mode 100644 index 000000000..83e3ab2f7 --- /dev/null +++ b/tests/test_issue3916_external_refresh_poll.py @@ -0,0 +1,24 @@ +import re +from pathlib import Path + +SESSIONS_JS = Path(__file__).resolve().parent.parent / "static" / "sessions.js" + + +def test_refreshActiveSessionIfExternallyUpdated_exists(): + src = SESSIONS_JS.read_text(encoding="utf-8") + assert "async function refreshActiveSessionIfExternallyUpdated" in src + + +def test_isExternalSession_guard_present(): + src = SESSIONS_JS.read_text(encoding="utf-8") + m = re.search( + r"async function refreshActiveSessionIfExternallyUpdated\b.*?^}", + src, + re.DOTALL | re.MULTILINE, + ) + assert m, "refreshActiveSessionIfExternallyUpdated function not found" + body = m.group(0) + assert re.search(r"if\s*\(\s*!\s*_isExternalSession\s*\(", body), ( + "refreshActiveSessionIfExternallyUpdated must have an early-return " + "guard: if(!_isExternalSession(...))" + ) From ab4d57fa7321366b35064cc814d7346c92c32cd8 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sun, 14 Jun 2026 19:23:39 +0000 Subject: [PATCH 3/3] Release OD (v0.51.417): #4193 + #4195 (sidebar issue-numbers + external-refresh guard); #4196 dropped (breaks norm_model_id multi-segment strip), #4194 dropped (bounced) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e40631f7f..9f2d308ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ## [Unreleased] +## [v0.51.417] — 2026-06-14 — Release OD (sidebar issue-number + external-refresh fixes, #4154/#3916) + +### Fixed + +- **GitHub issue numbers in session titles are no longer stripped from the sidebar (#4154).** The sidebar tag extractor treated any `#word` as an attention tag and removed it from the displayed title, so a title like "Fix #1234" lost its issue reference. Purely-numeric `#NNNN` patterns are now preserved while real attention tags (`#approval`, `#clarify`, …) still extract. (#4154) +- **The 30-second external-refresh poll no longer fires for non-external sessions (#3916).** `refreshActiveSessionIfExternallyUpdated` now early-returns for WebUI-native sessions, which don't need the external-update reconciliation, avoiding needless reload churn. (#3916) + ## [v0.51.416] — 2026-06-14 — Release OC (cache + single-flight /api/sessions payloads, #3791) ### Fixed