Merge #3203: clarify sidebar session tooltips

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
nesquena-hermes
2026-05-30 21:43:03 +00:00
3 changed files with 76 additions and 4 deletions
+7
View File
@@ -3,6 +3,13 @@
## [Unreleased]
## [v0.51.179] — 2026-05-30 — Release EY (stage-batch61 — custom-provider reasoning efforts + clearer sidebar tooltips)
### Fixed
- Reasoning effort selector now appears for thinking-capable models served through custom API aggregators (New API, One API, etc.) that use non-standard model naming — bare names like `deepseek-v4-flash` or dot-separated `moonshotai.kimi-k2.5` rather than the OpenRouter-style `vendor/model` slash format. The heuristic now also strips a dot-vendor prefix and recognizes a `thinking`/`reasoning` token anywhere in the model name; plain non-reasoning models stay hidden as before (#3202).
- Sidebar session row tooltips now explain the fork, prior-turn, child-session, and running/unread status badges, and hovering a truncated chat title shows the full title instead of the old "Double-click to rename" hint (#3203).
## [v0.51.178] — 2026-05-30 — Release EX (stage-batch60 — parallel sharded CI test runs)
### Changed
+35 -4
View File
@@ -3339,6 +3339,36 @@ function _sessionTitleForForkParent(parentSid){
return title;
}
function _sessionFullTitleTooltip(rawTitle, cleanTitle){
const fallback=String(cleanTitle||'Untitled').trim()||'Untitled';
const full=String(rawTitle||fallback).trim()||fallback;
if(full.startsWith('[SYSTEM:')) return fallback;
return full;
}
function _sessionForkTooltip(parentLabel){
const parent=String(parentLabel||'').trim()||'unknown parent';
return `Forked conversation — parent: ${parent}`;
}
function _sessionLineageBadgeTooltip(label, canExpand){
const base=String(label||'Prior turns').trim()||'Prior turns';
return canExpand
? `${base} — earlier context turns are collapsed here. Click to show or hide them.`
: `${base} — earlier context turns are collapsed here.`;
}
function _sessionChildBadgeTooltip(label){
const base=String(label||'Child sessions').trim()||'Child sessions';
return `${base} — child conversations spawned from this session. Click to show or hide them.`;
}
function _sessionStateTooltip({isStreaming=false,hasUnread=false}={}){
if(isStreaming) return 'Conversation is running';
if(hasUnread) return 'Unread completion';
return '';
}
function _attachChildSessionsToSidebarRows(collapsedRows, rawSessions){
const rows=(collapsedRows||[]).filter(s=>!_isChildSession(s)).map(s=>({...s}));
const visibleBySid=new Map();
@@ -4054,7 +4084,7 @@ function renderSessionListFromCache(){
branchInd.className='session-branch-indicator';
branchInd.innerHTML=li('git-branch',12);
const parentLabel=_sessionTitleForForkParent(s.parent_session_id)||_truncatedSessionId(s.parent_session_id);
branchInd.title=(typeof t==='function'?t('forked_from'):'Forked from')+' '+parentLabel;
branchInd.title=_sessionForkTooltip(parentLabel);
titleRow.appendChild(branchInd);
}
const title=document.createElement('span');
@@ -4063,7 +4093,7 @@ function renderSessionListFromCache(){
const titleMatched=Boolean(searchQueryRaw&&displayTitle.toLowerCase().includes(searchQueryRaw.toLowerCase()));
if(titleMatched) _appendHighlightedText(title,displayTitle,searchQueryRaw,'session-search-hit');
else title.textContent=displayTitle;
title.title=readOnly?'Read-only imported session':'Double-click to rename';
title.title=_sessionFullTitleTooltip(rawTitle,cleanTitle);
const tsMs=_sessionTimestampMs(s);
const ts=document.createElement('span');
const hasAttentionState=isStreaming||hasUnread||Boolean(attention);
@@ -4099,7 +4129,7 @@ function renderSessionListFromCache(){
segmentCountEl.className='session-lineage-count'+(canExpandLineageSegments?' expandable':'');
const segmentLabel=t('session_meta_segments', segmentCount);
segmentCountEl.textContent=segmentLabel;
segmentCountEl.title=segmentLabel;
segmentCountEl.title=_sessionLineageBadgeTooltip(segmentLabel,canExpandLineageSegments);
if(canExpandLineageSegments){
segmentCountEl.setAttribute('role','button');
segmentCountEl.setAttribute('tabindex','0');
@@ -4128,7 +4158,7 @@ function renderSessionListFromCache(){
childCountEl.className='session-child-count';
const childLabel=t('session_meta_children', childCount);
childCountEl.textContent=childLabel;
childCountEl.title=childLabel;
childCountEl.title=_sessionChildBadgeTooltip(childLabel);
['pointerdown','pointerup','click'].forEach(ev=>childCountEl.addEventListener(ev,e=>e.stopPropagation()));
childCountEl.onclick=(e)=>{
e.stopPropagation();
@@ -4317,6 +4347,7 @@ function renderSessionListFromCache(){
state.className='session-attention-indicator session-state-indicator'+(isStreaming?' is-streaming':(hasUnread?' is-unread':''))+attentionDotClass;
if(attention&&attention.title) state.title=attention.title;
state.setAttribute('aria-hidden','true');
state.title=_sessionStateTooltip({isStreaming,hasUnread});
el.appendChild(state);
// Single trigger button that opens a shared dropdown menu
let actions=null;
+34
View File
@@ -0,0 +1,34 @@
"""Sidebar tooltip contract tests."""
from pathlib import Path
REPO_ROOT = Path(__file__).parent.parent.resolve()
SESSIONS_JS_PATH = REPO_ROOT / "static" / "sessions.js"
def _sessions_js() -> str:
return SESSIONS_JS_PATH.read_text(encoding="utf-8")
def test_session_title_hover_shows_full_title_not_rename_hint():
"""The truncated sidebar title needs a real full-title tooltip.
The old "Double-click to rename" title hid the only native hover affordance
that can reveal a long chat title when badges/tags squeeze the row.
"""
js = _sessions_js()
assert "Double-click to rename" not in js
assert "title.title=_sessionFullTitleTooltip(rawTitle,cleanTitle);" in js
def test_sidebar_status_badges_have_explanatory_tooltips():
"""Compact badges/icons must explain what they mean, not repeat the chip text."""
js = _sessions_js()
assert "function _sessionFullTitleTooltip" in js
assert "function _sessionForkTooltip" in js
assert "function _sessionLineageBadgeTooltip" in js
assert "function _sessionChildBadgeTooltip" in js
assert "function _sessionStateTooltip" in js
assert "branchInd.title=_sessionForkTooltip(parentLabel);" in js
assert "segmentCountEl.title=_sessionLineageBadgeTooltip(segmentLabel,canExpandLineageSegments);" in js
assert "childCountEl.title=_sessionChildBadgeTooltip(childLabel);" in js
assert "state.title=_sessionStateTooltip({isStreaming,hasUnread});" in js