@@ -3,7 +3,10 @@ import SwiftUI
33struct LiveTailView: View {
44 @Environment(AppState.self) private var appState
55 @State private var viewModel = LiveTailViewModel()
6− @State private var selectedRecord: LogRecord?
6+ /// Selection is tracked by entry id, not by record value: live-tail rows
7+ /// frequently share identical content (repeated log lines), so comparing by
8+ /// record value would select/toggle every matching row at once.
9+ @State private var selectedEntryID: UUID?
710 @State private var autoScroll = true
811 @State private var pulseOpacity: Double = 1.0
912 @State private var showColumnPopover = false
@@ -112,7 +115,7 @@ struct LiveTailView: View {
112115
113116 Button {
114117 viewModel.clear()
115− selectedRecord = nil
118+ selectedEntryID = nil
116119 sortColumn = nil
117120 columnWidths = [:]
118121 cachedSorted = []
@@ -250,8 +253,9 @@ struct LiveTailView: View {
250253 .frame(minWidth: 400)
251254
252255 // Detail pane
253− if let record = selectedRecord {
254− LogDetailView(record: record)
256+ if let id = selectedEntryID,
257+ let entry = cachedSorted.first(where: { $0.id == id }) {
258+ LogDetailView(record: entry.record)
255259 .frame(minWidth: 280, idealWidth: 320)
256260 }
257261 }
@@ -263,7 +267,7 @@ struct LiveTailView: View {
263267 viewModel.stop()
264268 }
265269 viewModel.clear()
266− selectedRecord = nil
270+ selectedEntryID = nil
267271 sortColumn = nil
268272 columnWidths = [:]
269273 cachedSorted = []
@@ -337,7 +341,7 @@ struct LiveTailView: View {
337341 record: entry.record,
338342 columns: viewModel.visibleColumns,
339343 columnWidths: columnWidths,
340− isSelected: selectedRecord == entry.record,
344+ isSelected: selectedEntryID == entry.id,
341345 isAlternate: index % 2 == 1,
342346 severity: severityByID[entry.id] ?? .unknown,
343347 severityColumns: severityColumnSet,
@@ -347,10 +351,10 @@ struct LiveTailView: View {
347351 }
348352 )
349353 .onTapGesture {
350− if selectedRecord == entry.record {
351− selectedRecord = nil
354+ if selectedEntryID == entry.id {
355+ selectedEntryID = nil
352356 } else {
353− selectedRecord = entry.record
357+ selectedEntryID = entry.id
354358 }
355359 }
356360 }
@@ -381,9 +385,11 @@ struct LiveTailView: View {
381385 rebuildSortedEntries()
382386 }
383387 // Scroll when the rendered list (`cachedSorted`) changes, targeting
384− // its last element. Keying off `entries.count` could fire before the
385− // rebuild, scrolling to an id not yet present (a silent no-op).
386− .onChange(of: cachedSorted.count) { _, _ in
388+ // its last element. Key off the last element's id rather than the
389+ // count: once the buffer is pinned at `maxEntries`, the count stops
390+ // changing while new rows keep arriving, so a count-based trigger
391+ // would silently stall auto-scroll.
392+ .onChange(of: cachedSorted.last?.id) { _, _ in
387393 guard autoScroll, sortColumn == nil else { return }
388394 if let target = cachedSorted.last {
389395 proxy.scrollTo(target.id, anchor: .bottom)