2026-04-30 21:32:48 +00:00
""" Regression tests for sidebar lineage collapse helpers. """
2026-05-01 04:47:36 +00:00
import json
2026-04-30 21:32:48 +00:00
import shutil
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path ( __file__ ) . parent . parent . resolve ( )
SESSIONS_JS_PATH = REPO_ROOT / " static " / " sessions.js "
NODE = shutil . which ( " node " )
pytestmark = pytest . mark . skipif ( NODE is None , reason = " node not on PATH " )
def _run_node ( source : str ) - > str :
2026-05-05 02:36:10 +00:00
# Pass source via stdin rather than `-e <source>` argv — the latter is
# capped at MAX_ARG_STRLEN (131072 bytes on Linux) and tests that embed
# the entire sessions.js file can exceed that. stdin has no such limit.
2026-04-30 21:32:48 +00:00
result = subprocess . run (
2026-05-05 02:36:10 +00:00
[ NODE ] ,
input = source ,
2026-04-30 21:32:48 +00:00
cwd = str ( REPO_ROOT ) ,
capture_output = True ,
text = True ,
timeout = 10 ,
)
if result . returncode != 0 :
raise RuntimeError ( result . stderr )
return result . stdout . strip ( )
def test_sidebar_lineage_collapse_keeps_latest_tip_and_counts_segments ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
eval ( extractFunc ( ' _sessionTimestampMs ' ) ) ;
2026-05-02 04:09:36 +00:00
eval ( extractFunc ( ' _isChildSession ' ) ) ;
2026-04-30 21:32:48 +00:00
eval ( extractFunc ( ' _sessionLineageKey ' ) ) ;
eval ( extractFunc ( ' _collapseSessionLineageForSidebar ' ) ) ;
const sessions = [
{ { session_id : ' root ' , title : ' Hermes WebUI ' , message_count : 10 , updated_at : 10 , last_message_at : 10 , _lineage_root_id : ' root ' , _lineage_tip_id : ' root ' } } ,
{ { session_id : ' tip ' , title : ' Hermes WebUI ' , message_count : 20 , updated_at : 20 , last_message_at : 20 , _lineage_root_id : ' root ' , _lineage_tip_id : ' tip ' } } ,
{ { session_id : ' solo ' , title : ' Other ' , message_count : 5 , updated_at : 15 , last_message_at : 15 } } ,
] ;
const collapsed = _collapseSessionLineageForSidebar ( sessions ) ;
console . log ( JSON . stringify ( collapsed ) ) ;
"""
2026-05-01 04:47:36 +00:00
collapsed = json . loads ( _run_node ( source ) )
by_sid = { row [ " session_id " ] : row for row in collapsed }
assert set ( by_sid ) == { " tip " , " solo " }
assert by_sid [ " tip " ] [ " _lineage_collapsed_count " ] == 2
assert [ seg [ " session_id " ] for seg in by_sid [ " tip " ] [ " _lineage_segments " ] ] == [ " tip " , " root " ]
def test_sidebar_active_state_can_fall_back_to_url_session_during_boot ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
global . S = { { session : null } } ;
global . window = { { location : { { pathname : ' /session/url-active ' , search : ' ' , hash : ' ' } } } } ;
eval ( extractFunc ( ' _sessionIdFromLocation ' ) ) ;
eval ( extractFunc ( ' _activeSessionIdForSidebar ' ) ) ;
console . log ( _activeSessionIdForSidebar ( ) ) ;
"""
assert _run_node ( source ) == " url-active "
def test_collapsed_lineage_contains_active_hidden_segment ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
eval ( extractFunc ( ' _sessionTimestampMs ' ) ) ;
2026-05-02 04:09:36 +00:00
eval ( extractFunc ( ' _isChildSession ' ) ) ;
2026-05-01 04:47:36 +00:00
eval ( extractFunc ( ' _sessionLineageKey ' ) ) ;
eval ( extractFunc ( ' _collapseSessionLineageForSidebar ' ) ) ;
eval ( extractFunc ( ' _sessionLineageContainsSession ' ) ) ;
const sessions = [
{ { session_id : ' root ' , title : ' Hermes WebUI ' , message_count : 10 , updated_at : 10 , last_message_at : 10 , _lineage_root_id : ' root ' , _lineage_tip_id : ' tip ' } } ,
{ { session_id : ' tip ' , title : ' Hermes WebUI ' , message_count : 20 , updated_at : 20 , last_message_at : 20 , _lineage_root_id : ' root ' , _lineage_tip_id : ' tip ' } } ,
] ;
const collapsed = _collapseSessionLineageForSidebar ( sessions ) ;
console . log ( JSON . stringify ( { { sid : collapsed [ 0 ] . session_id , containsRoot : _sessionLineageContainsSession ( collapsed [ 0 ] , ' root ' ) } } ) ) ;
"""
result = _run_node ( source )
assert ' " sid " : " tip " ' in result
assert ' " containsRoot " :true ' in result
2026-05-02 04:09:36 +00:00
2026-05-08 19:21:25 +00:00
def test_stale_optimistic_compression_tips_collapse_even_when_parents_are_visible ( ) :
""" Active compression can leave old streaming tips in browser memory.
The server / index already expose only the latest tip , but client - side
optimistic rows from previous tips may still include parent_session_id links .
Those rows carry explicit lineage metadata and must collapse as one sidebar
conversation instead of rendering 7 / 8 / 9 / 10 segment duplicates .
"""
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
eval ( extractFunc ( ' _sessionTimestampMs ' ) ) ;
eval ( extractFunc ( ' _isChildSession ' ) ) ;
eval ( extractFunc ( ' _sessionLineageKey ' ) ) ;
eval ( extractFunc ( ' _collapseSessionLineageForSidebar ' ) ) ;
const sessions = [
{ { session_id : ' seg7 ' , title : ' Graphify ' , parent_session_id : ' seg6 ' , message_count : 1141 , updated_at : 70 , last_message_at : 70 , _lineage_root_id : ' root ' , _compression_segment_count : 7 } } ,
{ { session_id : ' seg8 ' , title : ' Graphify ' , parent_session_id : ' seg7 ' , message_count : 1254 , updated_at : 80 , last_message_at : 80 , _lineage_root_id : ' root ' , _compression_segment_count : 8 , pending_user_message : ' old ' } } ,
{ { session_id : ' seg9 ' , title : ' Graphify ' , parent_session_id : ' seg8 ' , message_count : 1404 , updated_at : 90 , last_message_at : 90 , _lineage_root_id : ' root ' , _compression_segment_count : 9 , active_stream_id : ' old-stream ' } } ,
{ { session_id : ' seg10 ' , title : ' Graphify ' , parent_session_id : ' seg9 ' , message_count : 1490 , updated_at : 100 , last_message_at : 100 , _lineage_root_id : ' root ' , _compression_segment_count : 10 , active_stream_id : ' current-stream ' } } ,
] ;
const collapsed = _collapseSessionLineageForSidebar ( sessions ) ;
console . log ( JSON . stringify ( collapsed ) ) ;
"""
collapsed = json . loads ( _run_node ( source ) )
assert [ row [ " session_id " ] for row in collapsed ] == [ " seg10 " ]
assert collapsed [ 0 ] [ " _lineage_collapsed_count " ] == 4
assert collapsed [ 0 ] [ " _compression_segment_count " ] == 10
assert [ seg [ " session_id " ] for seg in collapsed [ 0 ] [ " _lineage_segments " ] ] == [ " seg10 " , " seg9 " , " seg8 " , " seg7 " ]
2026-05-07 08:26:01 +00:00
2026-05-10 15:04:33 +00:00
def test_sidebar_lineage_collapse_prefers_highest_compression_segment_over_touched_parent ( ) :
""" A touched parent segment must not hide the newer compressed tip.
Opening or polling an older segment can refresh its updated_at without adding
messages . The collapsed sidebar row must still pick the highest compression
segment , otherwise the visible chat jumps back to a parent that lacks the
completed assistant answer .
"""
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
eval ( extractFunc ( ' _sessionTimestampMs ' ) ) ;
eval ( extractFunc ( ' _isChildSession ' ) ) ;
eval ( extractFunc ( ' _sessionLineageKey ' ) ) ;
eval ( extractFunc ( ' _collapseSessionLineageForSidebar ' ) ) ;
const sessions = [
{ { session_id : ' seg13 ' , title : ' Schaue dir die Release (fork) ' , message_count : 2490 , updated_at : 200 , last_message_at : 200 , _lineage_root_id : ' root ' , _compression_segment_count : 13 } } ,
{ { session_id : ' seg14 ' , title : ' Schaue dir die Release (fork) ' , message_count : 2532 , updated_at : 150 , last_message_at : 150 , _lineage_root_id : ' root ' , _compression_segment_count : 14 } } ,
] ;
const collapsed = _collapseSessionLineageForSidebar ( sessions ) ;
console . log ( JSON . stringify ( collapsed ) ) ;
"""
collapsed = json . loads ( _run_node ( source ) )
assert [ row [ " session_id " ] for row in collapsed ] == [ " seg14 " ]
assert collapsed [ 0 ] [ " _lineage_collapsed_count " ] == 2
assert [ seg [ " session_id " ] for seg in collapsed [ 0 ] [ " _lineage_segments " ] ] == [ " seg14 " , " seg13 " ]
2026-05-07 08:26:01 +00:00
2026-05-02 04:09:36 +00:00
def test_sidebar_attaches_child_sessions_to_collapsed_hidden_parent_lineage ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
eval ( extractFunc ( ' _sessionTimestampMs ' ) ) ;
eval ( extractFunc ( ' _isChildSession ' ) ) ;
eval ( extractFunc ( ' _sessionLineageKey ' ) ) ;
eval ( extractFunc ( ' _sidebarLineageKeyForRow ' ) ) ;
eval ( extractFunc ( ' _collapseSessionLineageForSidebar ' ) ) ;
eval ( extractFunc ( ' _attachChildSessionsToSidebarRows ' ) ) ;
const raw = [
{ { session_id : ' root ' , title : ' Root ' , updated_at : 10 , last_message_at : 10 , _lineage_root_id : ' root ' , _lineage_tip_id : ' tip ' } } ,
{ { session_id : ' tip ' , title : ' Tip ' , updated_at : 20 , last_message_at : 20 , _lineage_root_id : ' root ' , _lineage_tip_id : ' tip ' } } ,
{ { session_id : ' child ' , title : ' Subtask ' , parent_session_id : ' tip ' , relationship_type : ' child_session ' , _parent_lineage_root_id : ' root ' , updated_at : 30 , last_message_at : 30 } } ,
] ;
const collapsed = _collapseSessionLineageForSidebar ( raw ) ;
const attached = _attachChildSessionsToSidebarRows ( collapsed , raw ) ;
console . log ( JSON . stringify ( attached ) ) ;
"""
rows = json . loads ( _run_node ( source ) )
assert [ row [ " session_id " ] for row in rows ] == [ " tip " ]
assert rows [ 0 ] [ " _child_session_count " ] == 1
assert rows [ 0 ] [ " _child_sessions " ] [ 0 ] [ " session_id " ] == " child "
2026-05-07 08:26:01 +00:00
def test_cross_surface_webui_child_session_remains_top_level_when_parent_is_messaging ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
eval ( extractFunc ( ' _isChildSession ' ) ) ;
eval ( extractFunc ( ' _sidebarLineageKeyForRow ' ) ) ;
eval ( extractFunc ( ' _attachChildSessionsToSidebarRows ' ) ) ;
const collapsed = [ { { session_id : ' telegram_parent ' , title : ' Telegram parent ' , source_label : ' Telegram ' } } ] ;
const raw = [
collapsed [ 0 ] ,
{ {
session_id : ' webui_tip ' ,
title : ' Current WebUI continuation ' ,
parent_session_id : ' telegram_parent ' ,
relationship_type : ' child_session ' ,
parent_source : ' telegram ' ,
source_label : ' Telegram ' ,
session_source : ' messaging ' ,
raw_source : ' telegram ' ,
_cross_surface_child_session : true ,
} } ,
] ;
const rows = _attachChildSessionsToSidebarRows ( collapsed , raw ) ;
console . log ( JSON . stringify ( rows ) ) ;
"""
rows = json . loads ( _run_node ( source ) )
assert [ row [ " session_id " ] for row in rows ] == [ " telegram_parent " , " webui_tip " ]
assert rows [ 1 ] . get ( " _orphan_child_session " ) is True
assert " _child_sessions " not in rows [ 0 ]
2026-05-08 15:38:09 +00:00
def test_session_segment_count_prefers_visible_collapsed_backend_and_materialized_counts ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
eval ( extractFunc ( ' _sessionSegmentCount ' ) ) ;
const cases = [
_sessionSegmentCount ( { { _lineage_collapsed_count : 3 , _compression_segment_count : 2 , _lineage_segments : [ { { session_id : ' a ' } } , { { session_id : ' b ' } } ] } } ) ,
_sessionSegmentCount ( { { _compression_segment_count : 25 } } ) ,
_sessionSegmentCount ( { { _lineage_segments : [ { { session_id : ' tip ' } } , { { session_id : ' root ' } } , { { session_id : ' older ' } } ] } } ) ,
_sessionSegmentCount ( { { _lineage_collapsed_count : 1 , _compression_segment_count : 1 } } ) ,
_sessionSegmentCount ( null ) ,
] ;
console . log ( JSON . stringify ( cases ) ) ;
"""
assert json . loads ( _run_node ( source ) ) == [ 3 , 25 , 3 , 0 , 0 ]
2026-05-09 01:49:10 +00:00
def test_sidebar_lineage_segment_badge_is_localized ( ) :
2026-05-08 15:38:09 +00:00
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
css = ( REPO_ROOT / " static " / " style.css " ) . read_text ( encoding = " utf-8 " )
assert " session-lineage-count " in js
assert " const segmentCount=_sessionSegmentCount(s); " in js
assert " t( ' session_meta_segments ' , segmentCount) " in js
assert " titleRow.appendChild(segmentCountEl); " in js
assert " .session-lineage-count { " in css
2026-05-09 01:49:10 +00:00
def test_lineage_segment_expansion_static_contract ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
css = ( REPO_ROOT / " static " / " style.css " ) . read_text ( encoding = " utf-8 " )
assert " const _expandedLineageKeys = new Set(); " in js
assert " session-lineage-count,.session-lineage-segments,.session-lineage-segment " in js
assert " segmentCountEl.setAttribute( ' aria-expanded ' " in js
assert " _expandedLineageKeys.has(lineageKey) " in js
assert " _expandedLineageKeys.add(lineageKey) " in js
assert " _expandedLineageKeys.delete(lineageKey) " in js
assert " className= ' session-lineage-segments ' " in js
assert " className= ' session-lineage-segment ' " in js
2026-05-09 02:39:44 +00:00
assert " const segTitle=seg.title||t( ' session_lineage_segment_untitled ' ); " in js
assert " row.title=t( ' session_lineage_segment_open ' ); " in js
2026-05-09 01:49:10 +00:00
assert " await loadSession(seg.session_id); " in js
assert " .session-lineage-count.expandable { " in css
assert " .session-lineage-count.expandable:hover " in css
assert " .session-lineage-segments { " in css
assert " .session-lineage-segment { " in css
def test_active_hidden_lineage_segment_auto_expands_parent ( ) :
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
source = f """
const src = { js ! r } ;
function extractFunc ( name ) { {
const re = new RegExp ( ' function \\ \\ s+ ' + name + ' \\ \\ s* \\ \\ ( ' ) ;
const start = src . search ( re ) ;
if ( start < 0 ) throw new Error ( name + ' not found ' ) ;
let i = src . indexOf ( ' {{ ' , start ) ;
let depth = 1 ; i + + ;
while ( depth > 0 & & i < src . length ) { {
if ( src [ i ] == = ' {{ ' ) depth + + ;
else if ( src [ i ] == = ' }} ' ) depth - - ;
i + + ;
} }
return src . slice ( start , i ) ;
} }
const _expandedChildSessionKeys = new Set ( ) ;
const _expandedLineageKeys = new Set ( ) ;
eval ( extractFunc ( ' _sidebarLineageKeyForRow ' ) ) ;
eval ( extractFunc ( ' _syncSidebarExpansionForActiveSession ' ) ) ;
const rows = [ { {
session_id : ' seg10 ' ,
_lineage_key : ' root ' ,
_lineage_segments : [
{ { session_id : ' seg10 ' , updated_at : 100 } } ,
{ { session_id : ' seg9 ' , updated_at : 90 } } ,
{ { session_id : ' seg8 ' , updated_at : 80 } } ,
] ,
} } ] ;
_syncSidebarExpansionForActiveSession ( rows , ' seg8 ' ) ;
console . log ( JSON . stringify ( { { lineage : [ . . . _expandedLineageKeys ] , child : [ . . . _expandedChildSessionKeys ] } } ) ) ;
"""
assert json . loads ( _run_node ( source ) ) == { " lineage " : [ " root " ] , " child " : [ ] }
2026-05-08 15:38:09 +00:00
2026-05-09 02:39:44 +00:00
def test_lineage_segment_locale_keys_are_defined_for_sidebar_locales ( ) :
2026-05-08 15:38:09 +00:00
i18n = ( REPO_ROOT / " static " / " i18n.js " ) . read_text ( encoding = " utf-8 " )
2026-05-09 02:39:44 +00:00
required = [
" session_meta_segments: " ,
" session_lineage_segment_untitled: " ,
" session_lineage_segment_open: " ,
]
locale_count = i18n . count ( " session_meta_messages: " )
for key in required :
assert i18n . count ( key ) > = locale_count , f " { key } missing from one or more locale blocks "