diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a60bb12..a9fff014c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/static/sessions.js b/static/sessions.js index 6ee0bb68a..2ead02c99 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -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; diff --git a/tests/test_sidebar_tooltips.py b/tests/test_sidebar_tooltips.py new file mode 100644 index 000000000..a5be58913 --- /dev/null +++ b/tests/test_sidebar_tooltips.py @@ -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