@@ -315,23 +315,7 @@ struct LogTableView: View {
315315 }
316316 .onChange(of: sortColumn) { _, _ in debouncedRebuildSort() }
317317 .onChange(of: sortAscending) { _, _ in debouncedRebuildSort() }
318− .onChange(of: selectedRecord) { _, newValue in
319− // The owner can drop the selection on its own (QueryViewModel
320− // clears it after every query). When the new result set equals the
321− // old one, `onChange(of: records)` doesn't fire, so nothing would
322− // reconcile the row highlight: the row stayed highlighted with no
323− // detail pane, and the next click on it merely "deselected" it.
324− if newValue == nil { selectedIndex = nil }
325− }
326318 .onAppear {
327− // This branch is unmounted while results are empty (e.g. across a
328− // stream switch, which clears them first), so `onChange(of: columns)`
329− // never sees the switch. Drop a sort column the new columns don't
330− // have — or hide — before sorting, or rows would be ordered by an
331− // invisible column with no header showing the sort.
332− if let sortColumn, !columns.contains(sortColumn) {
333− self.sortColumn = nil
334− }
335319 recomputeColumnWidths(columns: columns, records: records)
336320 rebuildSort()
337321 }
@@ -417,9 +401,6 @@ struct LogTableView: View {
417401 }
418402
419403 private func rebuildSort() {
420− // A debounced rebuild scheduled before the table unmounted must not land
421− // after (and overwrite) this synchronous one.
422− sortTask?.cancel()
423404 let sevCols = buildSeverityColumnSet(columns: columns)
424405 let enumerated = Array(records.enumerated())
425406 let sorted: [(offset: Int, element: LogRecord)]
@@ -495,28 +476,12 @@ struct LogHeaderView: View {
495476
496477 @State private var draggedColumn: String?
497478 @State private var dropTargetColumn: String?
498− /// `.contextMenu`'s builder runs during body evaluation, so the filter menus
499− /// compute their values while the view is being rendered. Writing an `@State`
500− /// dictionary from there is "modifying state during view update" (undefined
501− /// behavior, and it re-invalidates the header). The cache is therefore a plain
502− /// reference that isn't observed; `recordsGeneration` — bumped outside body
503− /// evaluation — is what invalidates it and triggers the re-render.
504− @State private var uniqueValuesCache = UniqueValuesCache()
505− @State private var recordsGeneration = 0
506−
507− private final class UniqueValuesCache {
508− var generation = -1
509− var values: [String: [JSONValue]] = [:]
510− }
479+ @State private var uniqueValuesCache: [String: [JSONValue]] = [:]
511480
512481 private static let maxFilterValues = 20
513482
514483 private func uniqueValues(for column: String) -> [JSONValue] {
515− if uniqueValuesCache.generation != recordsGeneration {
516− uniqueValuesCache.generation = recordsGeneration
517− uniqueValuesCache.values = [:]
518− }
519− if let cached = uniqueValuesCache.values[column] {
484+ if let cached = uniqueValuesCache[column] {
520485 return cached
521486 }
522487 var seen = Set<JSONValue>()
@@ -528,7 +493,7 @@ struct LogHeaderView: View {
528493 }
529494 }
530495 let sorted = result.sorted()
531− uniqueValuesCache.values[column] = sorted
496+ uniqueValuesCache[column] = sorted
532497 return sorted
533498 }
534499
@@ -671,7 +636,7 @@ struct LogHeaderView: View {
671636 .background(Color(nsColor: .controlBackgroundColor))
672637 .border(Color.secondary.opacity(0.2), width: 0.5)
673638 .onChange(of: records) { _, _ in
674− recordsGeneration &+= 1
639+ uniqueValuesCache = [:]
675640 }
676641 }
677642}
@@ -704,26 +669,14 @@ private struct ColumnDropDelegate: DropDelegate {
704669 }
705670
706671 func performDrop(info: DropInfo) -> Bool {
707− guard let dragged = draggedColumn, dragged != column,
708− let provider = info.itemProviders(for: [UTType.plainText]).first else {
672+ guard let dragged = draggedColumn, dragged != column else {
709673 draggedColumn = nil
710674 dropTargetColumn = nil
711675 return false
712676 }
677+ onMoveColumn?(dragged, column)
713678 draggedColumn = nil
714679 dropTargetColumn = nil
715− // `draggedColumn` is only cleared by a drop on a header, so a header drag
716− // that was cancelled (or dropped elsewhere) leaves it set, and any later
717− // plain-text drag onto a header would move that stale column. Only move
718− // when the dropped payload really is the column header being dragged.
719− let target = column
720− let move = onMoveColumn
721− _ = provider.loadObject(ofClass: NSString.self) { object, _ in
722− guard let payload = object as? NSString, payload as String == dragged else { return }
723− DispatchQueue.main.async {
724− move?(dragged, target)
725− }
726− }
727680 return true
728681 }
729682