Summary
Two related staleness gaps in link-highlight mode, surfaced (with file:line evidence) during the verified research for #75 / PR #76. Both pre-date that work; both affect the badge display itself — the row-count estimate consumes the same stale state as the formatter, so scroll math stays self-consistent throughout.
1. Links are not rescanned on filter changes
rescan_links_if_active (crates/fdemon-app/src/handler/scroll.rs:128-155) runs from every scroll handler, but the filter-change arms (CycleLevelFilter / CycleSourceFilter / ResetFilters, crates/fdemon-app/src/handler/update.rs:~715-734) never call it. Changing a filter while link mode is active leaves LinkHighlightState.links describing the previous filtered viewport: badges can point at entries that are no longer visible, and newly visible entries get no badges until the next scroll.
Repro: enter link mode (L) on a view with links, press the level-filter cycle key — badges/shortcuts now disagree with what's on screen; press a stale shortcut and the jump targets the wrong (hidden) entry.
2. DetectedLink.entry_index is a raw buffer index that shifts on eviction
scan_viewport stores the raw VecDeque index (crates/fdemon-app/src/hyperlinks.rs:285-380). While link mode is active on a chatty session, ring-buffer eviction (Session::add_log pop_front) shifts every raw index down by one per evicted entry — badges (and shortcut jumps) silently reattach to neighboring entries. New-log arrival also doesn't trigger a rescan.
Fix directions
- Cheapest: call
rescan_links_if_active from the filter-change arms (mirrors the scroll handlers) and after eviction while link mode is active — links are already viewport-scoped and cheap to rebuild (≤35).
- Sturdier: key
DetectedLink by LogEntry.id (stable, monotonic) instead of raw index, converting at scan time; consumers (format_entry, format_stack_frame_line_with_links, entry_display_rows_cached's bypass, SelectLink) compare against entry.id. This eliminates the whole index-shift class and makes rescan timing a pure freshness concern.
Acceptance
- Filter change in link mode → badges match the new visible set immediately.
- Continuous log arrival + eviction in link mode → existing badges stay attached to the same entries (or are rescanned away), never reattach to neighbors.
- Tests for both, plus one asserting the row-count estimate still equals rendered rows through a mid-link-mode rescan.
Summary
Two related staleness gaps in link-highlight mode, surfaced (with file:line evidence) during the verified research for #75 / PR #76. Both pre-date that work; both affect the badge display itself — the row-count estimate consumes the same stale state as the formatter, so scroll math stays self-consistent throughout.
1. Links are not rescanned on filter changes
rescan_links_if_active(crates/fdemon-app/src/handler/scroll.rs:128-155) runs from every scroll handler, but the filter-change arms (CycleLevelFilter/CycleSourceFilter/ResetFilters,crates/fdemon-app/src/handler/update.rs:~715-734) never call it. Changing a filter while link mode is active leavesLinkHighlightState.linksdescribing the previous filtered viewport: badges can point at entries that are no longer visible, and newly visible entries get no badges until the next scroll.Repro: enter link mode (
L) on a view with links, press the level-filter cycle key — badges/shortcuts now disagree with what's on screen; press a stale shortcut and the jump targets the wrong (hidden) entry.2.
DetectedLink.entry_indexis a raw buffer index that shifts on evictionscan_viewportstores the rawVecDequeindex (crates/fdemon-app/src/hyperlinks.rs:285-380). While link mode is active on a chatty session, ring-buffer eviction (Session::add_logpop_front) shifts every raw index down by one per evicted entry — badges (and shortcut jumps) silently reattach to neighboring entries. New-log arrival also doesn't trigger a rescan.Fix directions
rescan_links_if_activefrom the filter-change arms (mirrors the scroll handlers) and after eviction while link mode is active — links are already viewport-scoped and cheap to rebuild (≤35).DetectedLinkbyLogEntry.id(stable, monotonic) instead of raw index, converting at scan time; consumers (format_entry,format_stack_frame_line_with_links,entry_display_rows_cached's bypass,SelectLink) compare againstentry.id. This eliminates the whole index-shift class and makes rescan timing a pure freshness concern.Acceptance