harden(#4020): prune lineage cache by collapsed-row key too (Opus SHOULD-FIX)

Opus advisor found a key-space asymmetry: _pruneLineageReportCacheToVisibleSessions
built visibleKeys from RAW rows via _sidebarLineageKeyForRow, but the render loop
keys the lineage-report cache by _sidebarLineageKeyForRow on the COLLAPSED row,
which can differ when collapse merges segments. On a malformed/edge chain the
expanded row's cache could be evicted every payload and re-fetched ~every 5s
(partial regression of the bug #4020 fixes). Fold the collapsed rows' cache keys
into the visible set too, mirroring the _resolveSessionIdFromSidebarLineage
precedent, behind a defensive try/catch.

docs(changelog): stamp #4020+#4055 as v0.51.373 (Release ML)
This commit is contained in:
nesquena-hermes
2026-06-13 00:03:30 +00:00
parent 0fdfa4b9fa
commit 89ca46eeb5
3 changed files with 26 additions and 1 deletions

View File

@@ -3,6 +3,13 @@
## [Unreleased] ## [Unreleased]
## [v0.51.373] — 2026-06-12 — Release ML (sidebar lineage + source-count fixes)
### Fixed
- **Expanded lineage segments no longer collapse when the session list refreshes mid-stream (#4005).** While a turn was streaming, each sidebar refresh wiped the lineage-report cache and re-collapsed any expanded lineage row. The cache is now pruned to only the rows that left the visible set (kept for still-visible rows), the in-flight report request is identity-guarded so a stale response can't overwrite a newer one, and an expanded row that still needs its report fetches and re-renders in place. (#4005)
- **The sidebar source-filter counts (WebUI / CLI) now match the number of rows actually shown (#3966).** The chip counts were computed from pre-collapse session totals, so they could disagree with the collapsed/grouped rows rendered in the list. Counts are now derived from the same collapse-and-attach pass used to render each source's rows. (#3966)
## [v0.51.372] — 2026-06-12 — Release MK (markdown link-label inline code, /use skill autocomplete, mobile Worklog overflow) ## [v0.51.372] — 2026-06-12 — Release MK (markdown link-label inline code, /use skill autocomplete, mobile Worklog overflow)
### Fixed ### Fixed

View File

@@ -4249,10 +4249,25 @@ function _clearLineageReportCache(){
function _pruneLineageReportCacheToVisibleSessions(sessions){ function _pruneLineageReportCacheToVisibleSessions(sessions){
const visibleKeys=new Set(); const visibleKeys=new Set();
for(const s of (Array.isArray(sessions)?sessions:[])){ const rows=Array.isArray(sessions)?sessions:[];
for(const s of rows){
const key=_sidebarLineageKeyForRow(s); const key=_sidebarLineageKeyForRow(s);
if(key) visibleKeys.add(key); if(key) visibleKeys.add(key);
} }
// Also retain the cache keys derived from the COLLAPSED/rendered rows. The
// render loop keys the lineage-report cache by _sidebarLineageKeyForRow on
// the COLLAPSED row (see renderSessionListFromCache), and collapse can merge
// segments so a collapsed row's key differs from any single raw input row's
// key. Mirroring the precedent in _resolveSessionIdFromSidebarLineage, fold
// the collapsed rows' keys in so a still-visible expanded row is never evicted
// (and re-fetched every payload) on a chain the raw keys alone wouldn't cover.
try{
for(const row of _collapseSessionLineageForSidebar(rows)){
if(!row||_isChildSession(row)) continue;
const key=_lineageReportCacheKey(row,_sidebarLineageKeyForRow(row));
if(key) visibleKeys.add(key);
}
}catch(_){ /* defensive: never let a prune-key derivation break list apply */ }
for(const key of Array.from(_lineageReportCache.keys())){ for(const key of Array.from(_lineageReportCache.keys())){
if(!visibleKeys.has(key)) _lineageReportCache.delete(key); if(!visibleKeys.has(key)) _lineageReportCache.delete(key);
} }

3
uv.lock generated Normal file
View File

@@ -0,0 +1,3 @@
version = 1
revision = 3
requires-python = ">=3.11"