git.iko.soy/parseable-macos
on master help sign in
Fix 2 bugs: unstable sort reshuffles tied rows, negative zero displays as -0
commit96828acdaac4d811f2f4dafdc73c95b79e0d5a30 authoriko <mail@iko.soy> dateSun 21 Jun 2026, 14:54 (3 months ago) parentd55225f
3 files changed+21−7
ParseableViewer/Models/JSONValue.swift+3 ParseableViewer/Views/LiveTailView.swift+9−5 ParseableViewer/Views/LogTableView.swift+9−2
ParseableViewer/Models/JSONValue.swift+3
@@ -55,6 +55,9 @@ enum JSONValue: Codable, Hashable, Sendable, Comparable {
5555 // letting String(_:) emit "inf"/"-inf"/"nan" into the UI and CSV cells.
5656 if !v.isFinite { return "null" }
5757 if v == v.rounded() && abs(v) < 1e15 {
58+ // Normalize negative zero so it renders as "0", not "-0"
59+ // (`%.0f` preserves the sign bit of -0.0).
60+ if v == 0 { return "0" }
5861 return String(format: "%.0f", v)
5962 }
6063 return String(v)
ParseableViewer/Views/LiveTailView.swift+9−5
@@ -322,11 +322,15 @@ struct LiveTailView: View {
322322 try? await Task.sleep(for: .milliseconds(150))
323323 guard !Task.isCancelled else { return }
324324 let sorted = await Task.detached(priority: .userInitiated) {
325− entries.sorted { a, b in
326− let aVal = a.record[col] ?? .null
327− let bVal = b.record[col] ?? .null
328− return asc ? aVal < bVal : bVal < aVal
329− }
325+ // `sorted(by:)` is not guaranteed stable and entry ids are random
326+ // UUIDs, so break ties on arrival order (the buffer index) to stop
327+ // equal-keyed rows from reshuffling on every poll.
328+ entries.enumerated().sorted { a, b in
329+ let aVal = a.element.record[col] ?? .null
330+ let bVal = b.element.record[col] ?? .null
331+ if aVal != bVal { return asc ? aVal < bVal : bVal < aVal }
332+ return a.offset < b.offset
333+ }.map(\.element)
330334 }.value
331335 guard !Task.isCancelled else { return }
332336 cachedSorted = sorted
ParseableViewer/Views/LogTableView.swift+9−2
@@ -359,7 +359,12 @@ struct LogTableView: View {
359359 sorted = enumerated.sorted { a, b in
360360 let aVal = a.element[col] ?? .null
361361 let bVal = b.element[col] ?? .null
362− return asc ? aVal < bVal : bVal < aVal
362+ // `sorted(by:)` is not guaranteed stable, so break ties on
363+ // the original index to keep equal-keyed rows in a fixed
364+ // order across re-sorts (otherwise low-cardinality columns
365+ // like `level`/`status` would reshuffle on every refresh).
366+ if aVal != bVal { return asc ? aVal < bVal : bVal < aVal }
367+ return a.offset < b.offset
363368 }
364369 } else {
365370 sorted = enumerated
@@ -403,7 +408,9 @@ struct LogTableView: View {
403408 sorted = enumerated.sorted { a, b in
404409 let aVal = a.element[sortColumn] ?? .null
405410 let bVal = b.element[sortColumn] ?? .null
406− return sortAscending ? aVal < bVal : bVal < aVal
411+ // Stable tiebreak on original index — see the async path above.
412+ if aVal != bVal { return sortAscending ? aVal < bVal : bVal < aVal }
413+ return a.offset < b.offset
407414 }
408415 } else {
409416 sorted = enumerated