fix(chat): keep compression tip selected in sidebar

This commit is contained in:
ai-ag2026 2026-05-25 08:28:12 +02:00
parent 4ea762ae0d
commit 3009c0bf71
3 changed files with 53 additions and 0 deletions

View file

@ -3,6 +3,10 @@
## [Unreleased]
### Fixed
- Sidebar compression lineage collapse now prefers the current continuation tip over a preserved parent snapshot when both rows share the same backend segment count. This keeps reloads after context compression from reopening the older parent transcript and making the active conversation appear to disappear.
## [v0.51.134] — 2026-05-25 — Release DF (stage-batch16 — single-PR Windows path defaults)
### Fixed

View file

@ -2817,6 +2817,13 @@ function _collapseSessionLineageForSidebar(sessions){
if(bSeg||aSeg){
if(bSeg!==aSeg) return bSeg-aSeg;
}
// Preserved pre-compression parents can share the same backend segment
// count as the continuation. Prefer the non-snapshot tip before falling
// back to timestamps, otherwise a recently-polled parent reopens the
// older transcript and makes the active continuation look lost.
const bSnapshot=!!(b&&b.pre_compression_snapshot);
const aSnapshot=!!(a&&a.pre_compression_snapshot);
if(bSnapshot!==aSnapshot) return aSnapshot-bSnapshot;
return _sessionTimestampMs(b)-_sessionTimestampMs(a);
});
const chosen=sorted[0];

View file

@ -253,6 +253,48 @@ console.log(JSON.stringify(collapsed));
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"]
def test_sidebar_attaches_child_sessions_to_collapsed_hidden_parent_lineage():
js = SESSIONS_JS_PATH.read_text(encoding="utf-8")
source = f"""