From fea8b2e17a698e28edd364ad4ed48af8587af514 Mon Sep 17 00:00:00 2001 From: aug0211 <659845+aug0211@users.noreply.github.com> Date: Sat, 9 May 2026 22:39:41 -0400 Subject: [PATCH] Bugfix: phantom ZT prediction dot intercepting latest BG dot on graph tap - Bugfix: tapping latest BG dot sometimes showed a stale ZT prediction popup rather than the actual CGM's BG reading - Fix: refresh predictions on BG update; drop entries within ~1 min of latest BG --- LoopFollow/Controllers/Graphs.swift | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/LoopFollow/Controllers/Graphs.swift b/LoopFollow/Controllers/Graphs.swift index 9789e9c46..c0a44a4a7 100644 --- a/LoopFollow/Controllers/Graphs.swift +++ b/LoopFollow/Controllers/Graphs.swift @@ -897,6 +897,11 @@ extension MainViewController { if autoScrollPauseUntil == nil || Date() > autoScrollPauseUntil! { BGChart.moveViewToAnimated(xValue: dateTimeUtils.getNowTimeIntervalUTC() - (BGChart.visibleXRange * 0.7), yValue: 0.0, axis: .right, duration: 1, easingOption: .easeInBack) } + + // Re-render OpenAPS predictions so any entry now at-or-before the latest BG + // is dropped from the dataset (otherwise the stale dot stays hit-testable + // beneath the new BG dot and steals the popup). + updateOpenAPSPredictionDisplay() } func updatePredictionGraph(color: UIColor? = nil) { @@ -1971,6 +1976,12 @@ extension MainViewController { let displayType: PredictionDisplayType = Storage.shared.device.value == "Loop" ? .lines : Storage.shared.predictionDisplayType.value let toLoad = Int(Storage.shared.predictionToLoad.value * 12) let predictionStart = openAPSPredUpdatedTime ?? Date().timeIntervalSince1970 + // Drop prediction entries within ~1 min of the latest BG. Anything closer + // either (a) is a stale entry sitting at-or-before the new BG and would + // steal the chart's hit-test, or (b) is the loop's "now" prediction that + // visually duplicates the BG dot. The first kept entry is always cleanly + // separated from the BG dot. + let predictionMinTime = bgData.last.map { $0.date + 60 } let predictionTypes: [(type: String, colorName: String, dataIndex: Int)] = [ ("ZT", "ZT", GraphDataIndex.ztPrediction.rawValue), @@ -1998,7 +2009,7 @@ extension MainViewController { for arr in allArrays where i < arr.count { valuesAtIndex.append(arr[i]) } - if !valuesAtIndex.isEmpty { + if !valuesAtIndex.isEmpty, predictionMinTime.map({ t > $0 }) ?? true { var yMin = max(valuesAtIndex.min()!, Double(globalVariables.minDisplayGlucose)) var yMax = min(valuesAtIndex.max()!, Double(globalVariables.maxDisplayGlucose)) // Ensure minimum ±1 mg/dL range so the cone is visible when predictions agree @@ -2031,7 +2042,9 @@ extension MainViewController { if i < graphdata.count { let v = graphdata[i] let clamped = min(max(Int(round(v)), globalVariables.minDisplayGlucose), globalVariables.maxDisplayGlucose) - predictionData.append(ShareGlucoseData(sgv: clamped, date: t, direction: "flat")) + if predictionMinTime.map({ t > $0 }) ?? true { + predictionData.append(ShareGlucoseData(sgv: clamped, date: t, direction: "flat")) + } t += 300 } } @@ -2062,15 +2075,7 @@ extension MainViewController { topPredictionBG = predictionVal + maxBGOffset } - if i == 0 { - if Storage.shared.showDots.value { - colors.append(color.withAlphaComponent(0.0)) - } else { - colors.append(color.withAlphaComponent(1.0)) - } - } else { - colors.append(color) - } + colors.append(color) let value = ChartDataEntry( x: predictionData[i].date,