git.iko.soy/parseable-macos
on master help sign in
Fix 3 bugs: shared JSONDecoder data race, stale selectedLogEntry after re-query, unreachable connection edit
129bc99iko
ParseableViewer/Services/ParseableClient.swift+4−1
@@ -96,7 +96,10 @@ final class ParseableClient: Sendable {
96 private let authHeader: String
96 private let authHeader: String
97 private let cache = ResponseCache()
97 private let cache = ResponseCache()
98
98
99+ // A fresh decoder per call. JSONDecoder.decode isn't documented as thread-safe,
100+ // and these methods run concurrently (e.g. schema/stats/info fetched in parallel),
101+ // so a single shared instance would be a data race. Constructing one is cheap.
99− private static nonisolated(unsafe) let jsonDecoder = JSONDecoder()
102+ private static var jsonDecoder: JSONDecoder { JSONDecoder() }
100
103
101 private static nonisolated(unsafe) let isoFormatter: ISO8601DateFormatter = {
104 private static nonisolated(unsafe) let isoFormatter: ISO8601DateFormatter = {
102 let f = ISO8601DateFormatter()
105 let f = ISO8601DateFormatter()
ParseableViewer/ViewModels/QueryViewModel.swift+4
@@ -402,6 +402,10 @@ final class QueryViewModel {
402 guard !Task.isCancelled else { return }
402 guard !Task.isCancelled else { return }
403
403
404 results = queryResults
404 results = queryResults
405+ // Drop any prior row selection: the new result set reassigns row
406+ // identities, so a retained selectedLogEntry would either dangle or
407+ // value-match an unrelated row, leaving a stale detail inspector.
408+ selectedLogEntry = nil
405 queryDuration = CFAbsoluteTimeGetCurrent() - startTime
409 queryDuration = CFAbsoluteTimeGetCurrent() - startTime
406 resultCount = results.count
410 resultCount = results.count
407
411
ParseableViewer/Views/SettingsView.swift+9
@@ -76,11 +76,20 @@ struct SettingsView: View {
76 .clipShape(RoundedRectangle(cornerRadius: 4))
76 .clipShape(RoundedRectangle(cornerRadius: 4))
77 }
77 }
78
78
79+ Button {
80+ appState.editingConnection = connection
81+ appState.showConnectionSheet = true
82+ } label: {
83+ Image(systemName: "pencil")
84+ }
85+ .help("Edit connection")
86+
79 Button(role: .destructive) {
87 Button(role: .destructive) {
80 appState.removeConnection(connection)
88 appState.removeConnection(connection)
81 } label: {
89 } label: {
82 Image(systemName: "trash")
90 Image(systemName: "trash")
83 }
91 }
92+ .help("Delete connection")
84 }
93 }
85 .padding(.vertical, 4)
94 .padding(.vertical, 4)
86 }
95 }