git.iko.soy/parseable-macos
on master help sign in
Fix 4 bugs: custom time range validation, health status flash, severity tinting lag, completion provider divergence
172566ciko
ParseableViewer/Views/LiveTailView.swift+7−4
@@ -311,10 +311,15 @@ struct LiveTailView: View {
311 // wake after its 150ms delay and overwrite the unsorted result with
311 // wake after its 150ms delay and overwrite the unsorted result with
312 // stale sorted entries.
312 // stale sorted entries.
313 liveTailSortTask?.cancel()
313 liveTailSortTask?.cancel()
314+ // Severity is keyed by entry id and is independent of row order, so
315+ // publish it synchronously in both paths. Deferring it into the
316+ // debounced sort task (as the sorted path used to) left rows tinted from
317+ // the previous severity-column set for ~150ms — or indefinitely if rapid
318+ // changes kept cancelling the task before it fired.
319+ severityColumnSet = sevCols
320+ severityByID = severityMap
314 guard let col = sortColumn else {
321 guard let col = sortColumn else {
315 cachedSorted = entries
322 cachedSorted = entries
316− severityColumnSet = sevCols
317− severityByID = severityMap
318 return
323 return
319 }
324 }
320 let asc = sortAscending
325 let asc = sortAscending
@@ -334,8 +339,6 @@ struct LiveTailView: View {
334 }.value
339 }.value
335 guard !Task.isCancelled else { return }
340 guard !Task.isCancelled else { return }
336 cachedSorted = sorted
341 cachedSorted = sorted
337− severityColumnSet = sevCols
338− severityByID = severityMap
339 }
342 }
340 }
343 }
341
344
ParseableViewer/Views/SQLAutoComplete.swift+20−6
@@ -108,14 +108,28 @@ enum SQLCompletionProvider {
108 let context = determineContext(text: text, position: wordStart)
108 let context = determineContext(text: text, position: wordStart)
109
109
110 // A table completion supplies its own surrounding quotes. If the user has
110 // A table completion supplies its own surrounding quotes. If the user has
111− // already typed an opening double-quote (common for stream names with
111+ // already typed quotes directly around the word (common for stream names
112− // spaces, e.g. `FROM "my`), absorb it into the replacement range so the
112+ // with spaces, e.g. `FROM "my"`), absorb them into the replacement range so
113− // inserted `"name"` replaces the stray quote instead of producing `""name"`.
113+ // the inserted `"name"` replaces the stray quotes instead of producing
114+ // `""name""`. Mirror `insertCompletion`, which absorbs leading and trailing
115+ // quotes independently for any `.table` item — so apply this wherever table
116+ // completions are offered (`.tableRef` and `.general`), not just `.tableRef`.
114 var replaceStart = wordStart
117 var replaceStart = wordStart
118+ var replaceEnd = wordEnd
119+ let offersTable: Bool
120+ switch context {
121+ case .tableRef, .general: offersTable = true
122+ default: offersTable = false
123+ }
124+ if offersTable {
115− if case .tableRef = context, wordStart > 0, nsText.character(at: wordStart - 1) == 0x22 {
125+ if wordStart > 0, nsText.character(at: wordStart - 1) == 0x22 {
116− replaceStart -= 1
126+ replaceStart -= 1
127+ }
128+ if wordEnd < nsText.length, nsText.character(at: wordEnd) == 0x22 {
129+ replaceEnd += 1
130+ }
117 }
131 }
118− let range = NSRange(location: replaceStart, length: wordEnd - replaceStart)
132+ let range = NSRange(location: replaceStart, length: replaceEnd - replaceStart)
119 let uppercasePrefix = prefix.uppercased()
133 let uppercasePrefix = prefix.uppercased()
120
134
121 var items: [SQLCompletionItem] = []
135 var items: [SQLCompletionItem] = []
ParseableViewer/Views/ServerInfoView.swift+19−6
@@ -41,12 +41,25 @@ struct ServerInfoView: View {
41 // Health status
41 // Health status
42 GroupBox("Health") {
42 GroupBox("Health") {
43 HStack {
43 HStack {
44+ // While the check is in flight, show a neutral state
45+ // rather than the default `isHealthy == false`, which
46+ // otherwise flashes a red "Unhealthy" dot on every
47+ // connection switch before the result resolves.
48+ if isLoading {
44− Circle()
49+ Circle()
50+ .fill(.gray)
51+ .frame(width: 12, height: 12)
52+ .accessibilityLabel("Checking health")
53+ Text("Checking…")
54+ .fontWeight(.medium)
55+ } else {
56+ Circle()
45− .fill(isHealthy ? .green : .red)
57+ .fill(isHealthy ? .green : .red)
46− .frame(width: 12, height: 12)
58+ .frame(width: 12, height: 12)
47− .accessibilityLabel(isHealthy ? "Healthy" : "Unhealthy")
59+ .accessibilityLabel(isHealthy ? "Healthy" : "Unhealthy")
48− Text(isHealthy ? "Healthy" : "Unhealthy")
60+ Text(isHealthy ? "Healthy" : "Unhealthy")
49− .fontWeight(.medium)
61+ .fontWeight(.medium)
62+ }
50 Spacer()
63 Spacer()
51 }
64 }
52 .padding(8)
65 .padding(8)
ParseableViewer/Views/TimeRangePicker.swift+10
@@ -31,6 +31,16 @@ struct TimeRangePicker: View {
31 .font(.caption)
31 .font(.caption)
32 }
32 }
33 }
33 }
34+ .onChange(of: showCustomPicker) { _, isShown in
35+ // Dismissing the popover by clicking outside bypasses the "Done"
36+ // button's `customEnd > customStart` validation, which could leave an
37+ // inverted or zero-width range selected (the menu already set
38+ // `option == .custom`). Sanitize on close so a backwards range can
39+ // never reach a query.
40+ if !isShown && customEnd <= customStart {
41+ customEnd = customStart.addingTimeInterval(3600)
42+ }
43+ }
34 .popover(isPresented: $showCustomPicker) {
44 .popover(isPresented: $showCustomPicker) {
35 VStack(spacing: 12) {
45 VStack(spacing: 12) {
36 Text("Custom Time Range")
46 Text("Custom Time Range")