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-18 00:35:38 +00:00
def test_parent_present_webui_compression_child_without_lineage_metadata_collapses ( ) :
""" WebUI-native compression continuations may only carry parent_session_id.
When both the preserved parent snapshot and the new continuation are present
in the sidebar payload , the continuation should still collapse with its
parent instead of appearing as a separate branch - like conversation ( #2489).
"""
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 : ' parent ' , title : ' Long WebUI conversation ' , message_count : 50 , updated_at : 10 , last_message_at : 10 , pre_compression_snapshot : true } } ,
{ { session_id : ' child ' , title : ' Long WebUI conversation ' , parent_session_id : ' parent ' , message_count : 12 , updated_at : 20 , last_message_at : 20 } } ,
] ;
const collapsed = _collapseSessionLineageForSidebar ( sessions ) ;
console . log ( JSON . stringify ( collapsed ) ) ;
"""
collapsed = json . loads ( _run_node ( source ) )
assert [ row [ " session_id " ] for row in collapsed ] == [ " child " ]
assert collapsed [ 0 ] [ " _lineage_key " ] == " parent "
assert collapsed [ 0 ] [ " _lineage_collapsed_count " ] == 2
assert [ seg [ " session_id " ] for seg in collapsed [ 0 ] [ " _lineage_segments " ] ] == [ " child " , " parent " ]
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-25 06:28:12 +00:00
def test_sidebar_lineage_collapse_prefers_current_tip_over_same_segment_snapshot ( ) :
""" A preserved parent snapshot can share the child ' s backend segment count.
Loading / polling the parent refreshes its timestamp , but the collapsed row
must still open the non - snapshot continuation tip . Otherwise a reload after
compression jumps back to the older parent transcript and looks like the
active conversation disappeared .
"""
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 : ' parent ' , title : ' Duplicate Assistant Text Blocks ' , message_count : 64 , updated_at : 300 , last_message_at : 300 , pre_compression_snapshot : true , _lineage_root_id : ' parent ' , _compression_segment_count : 2 } } ,
{ { session_id : ' child ' , title : ' Duplicate Assistant Text Blocks ' , parent_session_id : ' parent ' , message_count : 86 , updated_at : 200 , last_message_at : 200 , _lineage_root_id : ' parent ' , _compression_segment_count : 2 } } ,
] ;
const collapsed = _collapseSessionLineageForSidebar ( sessions ) ;
console . log ( JSON . stringify ( collapsed ) ) ;
"""
collapsed = json . loads ( _run_node ( source ) )
assert [ row [ " session_id " ] for row in collapsed ] == [ " child " ]
assert collapsed [ 0 ] [ " _lineage_collapsed_count " ] == 2
assert [ seg [ " session_id " ] for seg in collapsed [ 0 ] [ " _lineage_segments " ] ] == [ " child " , " parent " ]
2026-05-25 06:47:53 +00:00
def test_direct_parent_restore_resolves_to_visible_compression_tip ( ) :
""" A stale /session/<parent> URL should reopen the visible continuation tip.
The sidebar payload may omit the archived pre - compression parent but still
include the latest continuation with lineage metadata pointing back to the
parent . Boot restore should use that visible tip instead of loading the old
parent transcript and making the continuation look lost .
"""
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 ) ;
} }
var _allSessions = [
{ { session_id : ' child ' , title : ' Duplicate Assistant Text Blocks ' , parent_session_id : ' parent ' , message_count : 86 , updated_at : 200 , last_message_at : 200 , _lineage_root_id : ' parent ' , _compression_segment_count : 2 } } ,
{ { session_id : ' other ' , title : ' Other ' , message_count : 4 , updated_at : 100 , last_message_at : 100 } } ,
] ;
eval ( extractFunc ( ' _sessionTimestampMs ' ) ) ;
eval ( extractFunc ( ' _isChildSession ' ) ) ;
eval ( extractFunc ( ' _sessionLineageKey ' ) ) ;
eval ( extractFunc ( ' _sessionLineageContainsSession ' ) ) ;
eval ( extractFunc ( ' _sidebarLineageKeyForRow ' ) ) ;
eval ( extractFunc ( ' _collapseSessionLineageForSidebar ' ) ) ;
eval ( extractFunc ( ' _resolveSessionIdFromSidebarLineage ' ) ) ;
console . log ( JSON . stringify ( { { parent : _resolveSessionIdFromSidebarLineage ( ' parent ' ) , child : _resolveSessionIdFromSidebarLineage ( ' child ' ) , other : _resolveSessionIdFromSidebarLineage ( ' other ' ) } } ) ) ;
"""
result = json . loads ( _run_node ( source ) )
assert result == { " parent " : " child " , " child " : " child " , " other " : " other " }
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-14 02:12:02 +00:00
def test_sidebar_lineage_segment_badge_is_detailed_density_only_and_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
2026-05-14 02:12:02 +00:00
assert " const density=(window._sidebarDensity=== ' detailed ' ? ' detailed ' : ' compact ' ); " in js
assert " const showLineageMetadata=density=== ' detailed ' ; " in js
assert " const segmentCount=showLineageMetadata?_sessionSegmentCount(s):0; " in js
assert " const lineageSegments=showLineageMetadata?_lineageSegmentsForRender(s,lineageKey):[]; " in js
assert " const needsLineageReport=showLineageMetadata?_lineageReportNeedsFetch(s,lineageKey,segmentCount):false; " in js
assert " const canExpandLineageSegments=showLineageMetadata&&Boolean( " in js
2026-05-08 15:38:09 +00:00
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
2026-05-12 10:02:49 +00:00
assert " const _lineageReportCache = new Map(); " in js
assert " const _lineageReportInflight = new Map(); " in js
2026-06-12 00:55:35 +00:00
assert " _pruneLineageReportCacheToVisibleSessions(_allSessions); " in js
2026-05-09 01:49:10 +00:00
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
2026-05-12 10:02:49 +00:00
assert " _fetchLineageReportForRow(s,lineageKey).then " in js
2026-06-12 00:55:35 +00:00
assert js . count ( " _fetchLineageReportForRow(s,lineageKey).then(()=>renderSessionListFromCache()); " ) == 2
2026-05-12 10:02:49 +00:00
assert " ' /api/session/lineage/report?session_id= ' " in js
assert " encodeURIComponent(s.session_id) " in js
2026-05-09 01:49:10 +00:00
assert " className= ' session-lineage-segments ' " in js
assert " className= ' session-lineage-segment ' " in js
2026-05-14 08:18:53 +00:00
assert " const segTitle=_sessionDisplayTitle(seg)||t( ' session_lineage_segment_untitled ' ); " in js
2026-05-09 02:39:44 +00:00
assert " row.title=t( ' session_lineage_segment_open ' ); " in js
2026-06-11 20:28:33 +00:00
assert " await loadSession(seg.session_id, { skipLineageResolve:true}); " in js
assert " await loadSession(child.session_id, { skipLineageResolve:true}); " in js
assert " if(!opts.skipLineageResolve && typeof _resolveSessionIdFromSidebarLineage=== ' function ' ) { " in js
2026-05-09 01:49:10 +00:00
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
2026-05-12 10:02:49 +00:00
def test_lineage_report_fetch_is_needed_only_when_backend_count_exceeds_materialized_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 ) ;
} }
const _lineageReportCache = new Map ( ) ;
const _lineageReportInflight = new Map ( ) ;
eval ( extractFunc ( ' _lineageReportCacheKey ' ) ) ;
eval ( extractFunc ( ' _lineageLocalSegmentCount ' ) ) ;
eval ( extractFunc ( ' _lineageReportNeedsFetch ' ) ) ;
const backendOnly = { { session_id : ' tip ' , _lineage_key : ' root ' , _compression_segment_count : 25 } } ;
const localFull = { {
session_id : ' tip ' ,
_lineage_key : ' root ' ,
_compression_segment_count : 2 ,
_lineage_segments : [ { { session_id : ' tip ' } } , { { session_id : ' root ' } } ] ,
} } ;
const before = _lineageReportNeedsFetch ( backendOnly , ' root ' , 25 ) ;
_lineageReportCache . set ( ' root ' , { { segments : [ { { session_id : ' tip ' } } , { { session_id : ' root ' } } ] } } ) ;
const afterCache = _lineageReportNeedsFetch ( backendOnly , ' root ' , 25 ) ;
const fullLocal = _lineageReportNeedsFetch ( localFull , ' root ' , 2 ) ;
console . log ( JSON . stringify ( { { before , afterCache , fullLocal } } ) ) ;
"""
assert json . loads ( _run_node ( source ) ) == { " before " : True , " afterCache " : False , " fullLocal " : False }
def test_cached_lineage_report_segments_merge_with_materialized_segments_without_duplicates_or_children ( ) :
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 _lineageReportCache = new Map ( ) ;
eval ( extractFunc ( ' _lineageReportCacheKey ' ) ) ;
eval ( extractFunc ( ' _lineageSegmentsForRender ' ) ) ;
_lineageReportCache . set ( ' root ' , { {
segments : [
{ { session_id : ' tip ' , title : ' Tip ' , role : ' tip ' , started_at : 30 } } ,
{ { session_id : ' root ' , title : ' Root ' , role : ' hidden_segment ' , started_at : 20 } } ,
{ { session_id : ' older ' , title : ' Older ' , role : ' hidden_segment ' , started_at : 10 } } ,
{ { session_id : ' child ' , title : ' Child ' , role : ' child_session ' , started_at : 40 } } ,
] ,
children : [ { { session_id : ' child ' , title : ' Child ' , role : ' child_session ' } } ] ,
} } ) ;
const row = { {
session_id : ' tip ' ,
_lineage_key : ' root ' ,
_lineage_segments : [ { { session_id : ' tip ' , title : ' Tip ' } } , { { session_id : ' root ' , title : ' Root ' } } ] ,
} } ;
const segments = _lineageSegmentsForRender ( row , ' root ' ) . map ( seg = > seg . session_id ) ;
console . log ( JSON . stringify ( segments ) ) ;
"""
assert json . loads ( _run_node ( source ) ) == [ " root " , " older " ]
def test_lineage_report_fetch_uses_endpoint_once_and_caches_result ( ) :
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 _lineageReportCache = new Map ( ) ;
const _lineageReportInflight = new Map ( ) ;
let _lineageReportCacheGeneration = 0 ;
const calls = [ ] ;
function api ( path ) { {
calls . push ( path ) ;
return Promise . resolve ( { { found : true , segments : [ { { session_id : ' tip ' } } , { { session_id : ' root ' } } ] } } ) ;
} }
eval ( extractFunc ( ' _lineageReportCacheKey ' ) ) ;
eval ( extractFunc ( ' _fetchLineageReportForRow ' ) ) ;
( async ( ) = > { {
const row = { { session_id : ' tip ' , _lineage_key : ' root ' } } ;
const [ first , second ] = await Promise . all ( [
_fetchLineageReportForRow ( row , ' root ' ) ,
_fetchLineageReportForRow ( row , ' root ' ) ,
] ) ;
await _fetchLineageReportForRow ( row , ' root ' ) ;
console . log ( JSON . stringify ( { {
calls ,
cached : _lineageReportCache . has ( ' root ' ) ,
same : first == = second ,
} } ) ) ;
} } ) ( ) . catch ( err = > { { console . error ( err ) ; process . exit ( 1 ) ; } } ) ;
"""
result = json . loads ( _run_node ( source ) )
assert result == {
" calls " : [ " /api/session/lineage/report?session_id=tip " ] ,
" cached " : True ,
" same " : True ,
}
2026-06-12 00:55:35 +00:00
def test_lineage_refresh_cache_prune_keeps_visible_keys_and_drops_missing_ones ( ) :
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 _lineageReportCache = new Map ( ) ;
const _lineageReportInflight = new Map ( ) ;
eval ( extractFunc ( ' _sidebarLineageKeyForRow ' ) ) ;
eval ( extractFunc ( ' _pruneLineageReportCacheToVisibleSessions ' ) ) ;
const visibleRequest = Promise . resolve ( { { found : true } } ) ;
const staleRequest = Promise . resolve ( { { found : true } } ) ;
_lineageReportCache . set ( ' root ' , { { segments : [ { { session_id : ' root ' } } ] } } ) ;
_lineageReportCache . set ( ' stale ' , { { segments : [ { { session_id : ' stale ' } } ] } } ) ;
_lineageReportInflight . set ( ' root ' , visibleRequest ) ;
_lineageReportInflight . set ( ' stale ' , staleRequest ) ;
_pruneLineageReportCacheToVisibleSessions ( [
{ { session_id : ' tip ' , _lineage_key : ' root ' } } ,
{ { session_id : ' child ' , parent_session_id : ' root ' } } ,
] ) ;
console . log ( JSON . stringify ( { {
cacheKeys : Array . from ( _lineageReportCache . keys ( ) ) ,
inflightKeys : Array . from ( _lineageReportInflight . keys ( ) ) ,
} } ) ) ;
"""
assert json . loads ( _run_node ( source ) ) == {
" cacheKeys " : [ " root " ] ,
" inflightKeys " : [ " root " ] ,
}
def test_pruned_lineage_inflight_request_cannot_repopulate_cache ( ) :
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 _lineageReportCache = new Map ( ) ;
const _lineageReportInflight = new Map ( ) ;
let _lineageReportCacheGeneration = 0 ;
let resolveApi ;
function api ( path ) { {
return new Promise ( resolve = > { {
resolveApi = ( ) = > resolve ( { { found : true , path , segments : [ { { session_id : ' stale ' } } ] } } ) ;
} } ) ;
} }
eval ( extractFunc ( ' _sidebarLineageKeyForRow ' ) ) ;
eval ( extractFunc ( ' _lineageReportCacheKey ' ) ) ;
eval ( extractFunc ( ' _pruneLineageReportCacheToVisibleSessions ' ) ) ;
eval ( extractFunc ( ' _fetchLineageReportForRow ' ) ) ;
( async ( ) = > { {
const staleRow = { { session_id : ' stale-tip ' , _lineage_key : ' stale ' } } ;
const request = _fetchLineageReportForRow ( staleRow , ' stale ' ) ;
_pruneLineageReportCacheToVisibleSessions ( [ { { session_id : ' tip ' , _lineage_key : ' root ' } } ] ) ;
resolveApi ( ) ;
await request ;
console . log ( JSON . stringify ( { {
staleCached : _lineageReportCache . has ( ' stale ' ) ,
staleInflight : _lineageReportInflight . has ( ' stale ' ) ,
visibleCached : _lineageReportCache . has ( ' root ' ) ,
} } ) ) ;
} } ) ( ) . catch ( err = > { { console . error ( err ) ; process . exit ( 1 ) ; } } ) ;
"""
assert json . loads ( _run_node ( source ) ) == {
" staleCached " : False ,
" staleInflight " : False ,
" visibleCached " : False ,
}
2026-05-09 01:49:10 +00:00
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 "
2026-05-13 05:41:41 +00:00
def test_session_meta_segments_softened_label_no_literal_segment_in_english ( ) :
""" Regression: the sidebar badge for compressed/lineage rows must not visibly
say ' X segments ' by default — the technical internal term should be replaced
with softer user - facing copy ( #2155).
This verifies the English base locale ' s session_meta_segments key so that
t ( ) fallback for untranslated locales also produces softened copy .
"""
import re
i18n_text = ( REPO_ROOT / ' static ' / ' i18n.js ' ) . read_text ( encoding = ' utf-8 ' )
# Locate the English base-locale block (first occurrence, before any _lang guard).
first_lang = i18n_text . index ( ' _lang: \' en \' ' )
second_lang = i18n_text . index ( ' _lang: ' , first_lang + 1 )
english_slice = i18n_text [ first_lang : second_lang ]
assert ' session_meta_segments: ' in english_slice , ' session_meta_segments missing from English locale '
# Capture only the arrow-function value (not the key name which also contains 'segment').
match = re . search (
r " session_meta_segments: \ s*( \ ( \ w+ \ ) \ s*=> \ s*[^,]+) " ,
english_slice ,
)
assert match , ' session_meta_segments value not found in English locale '
rendered = match . group ( 1 )
assert ' segment ' not in rendered , (
f " session_meta_segments English value still contains the technical word ' segment ' : { rendered } . "
" Expected softened copy like ' prior turn(s) ' instead. See #2155. "
)
2026-05-14 08:18:53 +00:00
def test_sidebar_search_and_rows_use_read_only_display_title ( ) :
""" Stale persisted titles should not drive sidebar search/render when display_title exists. """
js = SESSIONS_JS_PATH . read_text ( encoding = " utf-8 " )
assert " function _sessionDisplayTitle " in js
assert " function _sessionTitleTags " in js
2026-05-30 17:49:10 +00:00
assert " function _sessionSearchDirectAndTitleMatches " in js
assert " const titleMatches=(sessions||[]).filter(s=>_sessionDisplayTitle(s).toLowerCase().includes(q)); " in js
assert " const directAndTitleMatches=_sessionSearchDirectAndTitleMatches(_allSessions,currentQ); " in js
2026-05-14 08:18:53 +00:00
assert " const rawTitle=_sessionDisplayTitle(s); " in js
assert " const tags=_sessionTitleTags(rawTitle); " in js
assert " const segTitle=_sessionDisplayTitle(seg)||t( ' session_lineage_segment_untitled ' ); " in js
assert " const childTitle=_sessionDisplayTitle(child)|| ' Untitled child session ' ; " in js
2026-05-14 13:10:50 +00:00
def test_child_session_parent_segment_note_uses_display_title ( ) :
""" A child attached through a hidden parent segment should show the reconciled segment title. """
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 ( ' _sessionDisplayTitle ' ) ) ;
eval ( extractFunc ( ' _attachChildSessionsToSidebarRows ' ) ) ;
const parentRow = { {
session_id : ' tip ' ,
title : ' Hermes WebUI #8 ' ,
_lineage_root_id : ' root ' ,
_lineage_segments : [
{ { session_id : ' tip ' , title : ' Hermes WebUI #8 ' , display_title : ' Hermes WebUI #177 ' } } ,
{ { session_id : ' old-parent ' , title : ' Hermes WebUI #8 ' , display_title : ' Hermes WebUI #176 ' } } ,
] ,
} } ;
const child = { {
session_id : ' child ' ,
title : ' Child Session ' ,
relationship_type : ' child_session ' ,
parent_session_id : ' old-parent ' ,
} } ;
const rows = _attachChildSessionsToSidebarRows ( [ parentRow ] , [ parentRow , child ] ) ;
console . log ( JSON . stringify ( rows [ 0 ] . _child_sessions [ 0 ] ) ) ;
"""
child = json . loads ( _run_node ( source ) )
assert child [ " _parent_segment_id " ] == " old-parent "
assert child [ " _parent_segment_title " ] == " Hermes WebUI #176 "
2026-05-14 08:18:53 +00:00
def test_default_webui_numbered_titles_are_not_treated_as_hash_tags ( ) :
""" The reconciled title ' Hermes WebUI #177 ' must render with its number intact. """
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 ( ' _sessionTitleIsDefaultWebUI ' ) ) ;
eval ( extractFunc ( ' _sessionTitleTags ' ) ) ;
console . log ( JSON . stringify ( { {
webui : _sessionTitleTags ( ' Hermes WebUI #177 ' ) ,
custom : _sessionTitleTags ( ' Deploy #prod ' ) ,
} } ) ) ;
"""
assert json . loads ( _run_node ( source ) ) == { " webui " : [ ] , " custom " : [ " #prod " ] }