Skip to content

**GraphDetailView:** Long-hold detail popup on glucose chart#2425

Open
taylorpatterson-T1D wants to merge 264 commits intoLoopKit:devfrom
TaylorJPatterson:feat/GraphDetailView
Open

**GraphDetailView:** Long-hold detail popup on glucose chart#2425
taylorpatterson-T1D wants to merge 264 commits intoLoopKit:devfrom
TaylorJPatterson:feat/GraphDetailView

Conversation

@taylorpatterson-T1D
Copy link
Copy Markdown

@taylorpatterson-T1D taylorpatterson-T1D commented May 2, 2026

Summary

Long-press anywhere on the glucose chart to see a detailed popup of everything happening at that moment — glucose, insulin, carbs, presets, heart rate and more. Scrub left and right to explore the timeline. Replaces the existing minimal touch highlight with a richer, more informative experience. Included in the Loop AI PowerPack.

Inspired by Issue #2257 and similar functionality seen in Trio.

The Problem We're Solving

Loop's glucose chart currently shows a minimal touch highlight when you long-press — just the glucose value at that point. Users frequently want to understand what was happening at a specific moment: Was a bolus delivered? What was IOB? Was a preset active? Was my heart rate elevated? Today, answering these questions requires navigating to multiple screens and mentally correlating timestamps.

This is especially frustrating when reviewing post-meal spikes, unexpected lows, or activity-related patterns. The data exists in Loop — it's just not accessible in context.

New Feature's Impact

GraphDetailView adds an interactive detail popup directly on the glucose chart. Long-press to see data at a point in time, then scrub left/right to explore the timeline without lifting your finger. The popup shows all relevant data at that moment in a clean, color-coded layout.

What it does

  • Long-press anywhere on the glucose chart to show a detail popup
  • Scrub left and right to slide through the timeline — the popup follows your finger and updates data in real-time
  • Haptic feedback ticks on each 1-minute boundary as you scrub
  • Auto-fades after 5 seconds when you lift your finger
  • Dismiss early by tapping elsewhere or the X button
  • Re-scrub without needing to dismiss first — just long-press again

Data shown at each point

Data Source Icon
Glucose GlucoseStore (±5 min window, closest sample) 🟢🟠🔴 color-coded by range
IOB DoseStore insulin-on-board values 🟠 syringe
COB LoopState carbs-on-board (recent only) 🟢 fork & knife
Bolus DoseStore normalized entries (±15 min window) 🔵 vial
Basal Rate Scheduled basal rate at that time 🩵 waveform
Preset Active schedule override at that time 🟣 slider
AutoPreset Active auto-detected preset (if AutoPresets enabled) 🟢 figure.walk
Heart Rate HealthKit heart rate samples (±5 min window) ❤️ heart

Behavior details

  • Popup positions to the upper-right of the touch point
  • If not enough room above, flips below the touch
  • Respects safe areas (notch/Dynamic Island) in both portrait and landscape
  • Auto-dismisses on device rotation
  • Won't navigate to PredictionTableViewController while popup is showing
  • Stale data from previous scrub position stays visible until new data loads (no flicker)
  • Data queries are throttled (150ms) during scrub to avoid flooding HealthKit/DoseStore

Standalone design

  • Zero dependencies on FoodFinder, LoopInsights, or AutoPresets
  • Works with stock Loop — shows glucose, IOB, COB, bolus, basal, preset, and heart rate
  • Enhanced when AutoPresets is installed: shows active auto-detected preset via runtime UserDefaults read (no compile-time dependency)
  • Future features can add data by extending GraphDetailData and GraphDetailViewModel

Architecture

  • 2 new files, following Loop's layer-based architecture
  • 2 existing files modified with minimal changes:
    • StatusTableViewController.swift — gesture handling, popup presentation/dismissal (~225 lines in a dedicated MARK section)
    • project.pbxproj — file references for new files
  • No changes to LoopKit, LoopAlgorithm, or any other submodules
  • Developed from v3.10.0 DEV branch

New files

File Location Purpose
GraphDetailView.swift Views/ SwiftUI popup view — header, data rows, formatting, color coding (215 lines)
GraphDetailViewModel.swift Managers/ Data aggregation — queries GlucoseStore, DoseStore, HealthKit, LoopState, UserDefaults for all data at a given timestamp (241 lines)

Integration points into Loop (2 files modified)

File What changed
StatusTableViewController.swift Added long-press gesture on glucose chart, popup presentation/positioning/dismissal, scrub handling, auto-fade timer. Disabled original chart highlight gesture. Added navigation guard while popup is showing.
project.pbxproj Added file references and build phase entries for the 2 new files

Screenshots

Portrait Mode

IMG_2163

Landscape Mode

IMG_2165

Installation

Option A — Merge branch directly

git remote add taylor https://github.com/TaylorJPatterson/Loop.git
git fetch taylor feat/GraphDetailView
git merge taylor/feat/GraphDetailView

Option B — Cherry-pick the commit

git remote add taylor https://github.com/TaylorJPatterson/Loop.git
git fetch taylor feat/GraphDetailView
git cherry-pick taylor/feat/GraphDetailView

Requesting review by @marionbarker based on availability.

taylorpatterson-T1D and others added 30 commits February 12, 2026 18:54
… advisor UI

Add Ask LoopInsights chat with AI advisor powered by therapy context and glucose data.
Background monitoring with configurable frequency and notification banners. New Trends
& Insights view with Daily/Weekly/Monthly/Stats/Advisor tabs. Dark gradient styling
for chat and trends views. Banner now includes Ask button to open chat directly.
…ports

Add clinical goal tracking (TIR, A1C, below-range, custom) with progress bars,
AI-powered 30-day pattern discovery with sick day and negative basal detection,
timestamped reflection journal with mood tags, and HTML-to-PDF report generation
with share sheet. Goals & Patterns accessible from the Dashboard navigation section.
… analysis

Add HealthKit biometric data (heart rate, HRV, steps, sleep, active energy, weight)
to the AI analysis and chat pipelines. Biometrics are read-only, independently
authorized, and gracefully degrade when individual types are unavailable.

New file: LoopInsights_HealthKitManager.swift
Modified: Models, DataAggregator, AIAnalysis, ChatViewModel, Coordinator,
FeatureFlags, SettingsView, DashboardView, pbxproj, Localizable.xcstrings
…nsights, Nightscout import

- Ambulatory Glucose Profile (AGP) chart with percentile bands and median line
- Clarity-style dashboard redesign: Glucose card, Time in Range 5-zone stacked bar,
  capsule period picker with exact Clarity colors (#C14F0C, #F0CA4C, #74A52E, #D36265, #7F0302)
- Caffeine tracker with half-life decay modeling and glucose correlation
- Meal insights with food response analysis and per-meal glucose impact
- Nightscout data import support
- Advanced analyzers for pattern detection
- 5-zone TIR breakdown (Very High/High/In Range/Low/Very Low) replacing 3-zone model
- Compact list section spacing for tighter dashboard layout
- Chat view UI refinements
…card fixes

P1: Parallel HealthKit queries via async let (6 concurrent fetches)
P2: Single-pass TIR zone counting (5-zone) replacing multiple filter passes
P3: Pre-fetch raw data in DataAggregator, cache for cross-component reuse
P4: Binary search for glucose lookups in FoodResponseAnalyzer
P5: Pre-sorted glucose samples with binary search in AdvancedAnalyzers
P6: Pre-compute AGP data in ViewModel instead of SwiftUI view body
P7: Static DateFormatter in LoopInsightsTimeBlock.formatTime
P8: Pre-sort schedule items before dose loops, pre-sort in ViewModel
P9: Pre-convert glucose to parallel arrays avoiding repeated doubleValue calls
P10: Pass precomputed hourly averages to circadian profile builder

Also: enhanced step/activity data in AI prompts with time-of-day breakdowns
and activity-glucose correlation analysis (2h lag), and meal card layout cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y fixes

Glucose chart now operates in two modes: standard Ambulatory Glucose Profile
(24-hour overlay with percentile bands) for 14-day lookback, and Glucose Profile
(multi-day time series) for all other periods. Both modes include an info button
explaining the visualization. HealthKit glucose data supplements Loop store for
longer analysis periods. Chart data clears on period change to prevent stale labels.

Additional fixes across 22 files: improved HealthKit data pipeline reliability,
enhanced test data provider, refined food response analysis, and minor bug fixes
in background monitor, coordinator, caffeine tracker, and goals/trends views.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y fixes

Glucose chart now operates in two modes: standard Ambulatory Glucose Profile
(24-hour overlay with percentile bands) for 14-day lookback, and Glucose Profile
(multi-day time series) for all other periods. Both modes include an info button
explaining the visualization. HealthKit glucose data supplements Loop store for
longer analysis periods. Chart data clears on period change to prevent stale labels.

Additional fixes across 22 files: improved HealthKit data pipeline reliability,
enhanced test data provider, refined food response analysis, and minor bug fixes
in background monitor, coordinator, caffeine tracker, and goals/trends views.
Bump all body text, headers, and stat values to full white for readability
on dark backgrounds. Replace .toolbarColorScheme (iOS 16+) with manual
toolbar principal title for compatibility. Restore UINavigationBarAppearance
approach in ChatView.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 20-char limit was truncating food names (e.g. "Baked pastry with f…")
which made them unreadable in LoopInsights Meal Insights. The RowEmojiTextField
maxLength only restricts keyboard input, so longer programmatic values are safe.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added steps for creating and using test data in developer mode for demos and feature functionality testing.
…ivity

CoreMotion-based activity detection that automatically applies user-selected
override presets when walking or running is detected. 7 new files, 2 modified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Safety guardrails (3 layers of defense against dangerous therapy values):
- LoopInsights_SafetyGuardrails struct with clinical bounds mirroring LoopKit
  (CR 4-28 recommended/2-150 absolute, ISF 16-400/10-500, Basal 0.05-10/0.05-30)
- Post-parse validation rejects values outside absolute bounds and >25% changes
- AI prompt now includes absolute bounds with clamping instructions
- confirmApply() hard-blocks absolute violations
- applyEditedSuggestion() validates edited blocks against absolute bounds
- autoApplySuggestion() blocks anything outside recommended range (stricter)
- SuggestionDetailView shows orange warning banner and color-coded values
- DashboardView alert changes to "Safety Warning" with specific warnings
- Suggestion cards show orange triangle badge for guardrail warnings

Data-first AI prompts (all 4 AI interaction points):
- Chat, Analysis, Goals/Patterns, and Trends prompts now require every
  answer to cite the user's specific numbers — no generic diabetes advice
- Added "#1 RULE" blocks emphasizing real data over textbook answers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Safety guardrails (3 layers of defense against dangerous therapy values):
- LoopInsights_SafetyGuardrails struct with clinical bounds mirroring LoopKit
  (CR 4-28 recommended/2-150 absolute, ISF 16-400/10-500, Basal 0.05-10/0.05-30)
- Post-parse validation rejects values outside absolute bounds and >25% changes
- AI prompt now includes absolute bounds with clamping instructions
- confirmApply() hard-blocks absolute violations
- applyEditedSuggestion() validates edited blocks against absolute bounds
- autoApplySuggestion() blocks anything outside recommended range (stricter)
- SuggestionDetailView shows orange warning banner and color-coded values
- DashboardView alert changes to "Safety Warning" with specific warnings
- Suggestion cards show orange triangle badge for guardrail warnings

Data-first AI prompts (all 4 AI interaction points):
- Chat, Analysis, Goals/Patterns, and Trends prompts now require every
  answer to cite the user's specific numbers — no generic diabetes advice
- Added "#1 RULE" blocks emphasizing real data over textbook answers
Combines FoodFinder (34 files) with LoopInsights (18 files) on a shared
history rooted in feat/LoopInsights. Resolves pbxproj, SettingsView, and
Localizable.xcstrings merge conflicts — both features coexist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ivity

CoreMotion-based activity detection that automatically applies user-selected
override presets when walking or running is detected. 7 new files, 2 modified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Passes the user's insulin type (e.g. Fiasp, Novolog) into the therapy
snapshot and AI prompts so the model can distinguish timing issues from
dosing issues based on pharmacokinetics.
# Conflicts:
#	Loop/Views/SettingsView.swift
The additionalSteps > 5 threshold was way too low — 9 steps in a
2-minute window was confirming as "Walking". Now requires a minimum
pace of 20 steps/min (40 steps for 120s window) to filter fidgeting
while still catching slow walks.
Pedometer fires callbacks every ~2.5s even with unchanged step count.
The stop timer was being restarted on every callback, preventing it
from ever reaching 0. Now only restarts when steps actually increase.
iOS delays timers when backgrounded (120s timer firing at 293s).
Using configured interval meant casual steps over 5 minutes passed a
bar designed for 2 minutes. Now uses actual elapsed time and requires
30 steps/min — e.g. 146 steps needed for a 293s delayed timer.
…stop timer

- Reject confirmation if last step was >30s ago (prevents walk-then-stop false positives)
- Require High Confidence now requires CoreMotion classifier to recently confirm activity
- Remove dead code from stop timer (additionalSteps check can never trigger)
CoreMotion classifier too slow/unreliable to gate confirmation.
Step-based checks (rate + recency) are sufficient. Backend code remains.
@taylorpatterson-T1D taylorpatterson-T1D changed the title Add GraphDetailView — long-hold detail popup on glucose chart **GraphDetailView:** Long-hold detail popup on glucose chart May 2, 2026
Only on feat/AllFeatures — individual feature branches keep stock Loop icon.
Replaces all 18 tracked PNGs in DerivedAssetsBase with purple PowerPack icon.
Track where you place insulin pump infusion sets and CGM sensors on a visual body map. Color-coded pins show site age (red = fresh, green = safe to reuse). Prompted logging on pump deactivation, manual logging anytime. Drag-and-drop pin adjustment with proximity-based coloring warns when placing near recently-used sites.

11 new files, 3 modified files, ~1,829 lines added.
- 12 recommended placement zones as grey ellipses on body map
- Toggleable zones via collapsible DisclosureGroup in Settings
- Pin expands to 110pt with halo on touch-drag
- Proximity-based color (green=safe, red=danger) during drag
- Body bounds clamping prevents pins outside silhouette
- Save button pinned to bottom of selection sheet
- Feature toggle takes effect immediately
- Updated body map images and app icon
- iOS 15 compatible (.strokeBorder instead of .stroke)
Loose PNGs in Resources/SiteAtlas/ weren't being included in the app
bundle despite correct pbxproj registration. Moved to imagesets in
DerivedAssetsBase.xcassets and simplified loading to Image("name").
CLGeocoder often returns shopping center names instead of the specific
restaurant. Added MKLocalSearch refinement that finds the closest food
venue within 100m and replaces the generic geocode name. Also updated
the AI prompt to include the restaurant name in the food title.
"Get help with Therapy Settings" now opens LoopInsights instead of the
blank DemoPlaceHolderView stub when LoopInsights is enabled.
onAppear on therapySettingsView fired too late — at the same instant
TherapySettingsView rendered its body, so the registry was still nil.
Moving it to SettingsView's top-level body ensures it fires when the
user opens Settings, before they navigate to Therapy Settings.
…ghts and AutoPresets

Internal data + thresholds stay canonical mg/dL; conversion happens at the
display + AI-prompt boundary via a new LoopInsights_GlucoseUnitContext helper.
AI prompts get a unit-context block so Claude responds in the user's unit;
canonical JSON fields (target_range_*_mgdl, ISF current/proposed_value) stay
mg/dL by explicit prompt instruction.

LoopInsights_Coordinator.init now requires displayGlucosePreference; the
dataStoresProvider tuple is extended from 5 to 6 elements. AutoPresets_Coordinator
and PreMealAdvisorService get the preference set during boot in LoopAppManager.

Display surfaces fixed: Dashboard, Trends, Meal Insights, Meal Debrief, Endo
Report, Caregiver Digest (text + HTML), Goals report, Suggestion Detail,
Settings tight-range stepper, AutoPresets override editor.

AI prompts updated: ChatViewModel, AIAnalysis (with mg/dL JSON-field rule for
ISF values), TrendsInsightsView, GoalsView, MealInsightsViewModel,
AutoPresets_AIAdvisor (with mg/dL JSON-field rule for target ranges).
A pizza on a paper menu was being identified off the menu text instead of
the visible plate. Three fixes:

- Require text bounding boxes to cover >75% of the image before routing
  through the menu-text path (previously: 5+ lines of any size triggered)
- Reframe menu-path prompt so visible food stays primary; OCR text is
  only used to identify the dish or restaurant
- Hoist LOCATION CONTEXT to the top of every prompt with REQUIRED
  venue-in-title and 📍-prefix rules that apply across image_types
Creates a second timed carb entry alongside the primary to cover the
delayed glucose rise from fat and protein. Toggle + slider live on
CarbEntryView; FoodFinder auto-populates macros and auto-flips the
toggle when computed FPU crosses the threshold (default 1.5).

- 10 new BolusPro files in Models/Resources/Services/Views (no LoopKit changes)
- Math: Trio gram formula (fat × 0.9 + protein × 0.4) × coverage % × slider
- Secondary NewCarbEntry post-dated +60 min, 6 hr absorption, foodType 🥩
- DataLayer bolusProEntry event (12 fields) for population analysis
- LoopInsights BehaviorInsights gains a BolusPro Patterns section
  (adoption, slider drift, auto-detect override, bonus distribution, source mix)
- Item exclusion in FoodFinder recomputes FPU and walks the toggle back
  when it drops below threshold (only when auto-detected)
- Documentation/BolusPro/ user + developer guides
Lifts the DataLayer event-recording call out of BolusPro_DataLayerHook
and into DataLayer_Coordinator's notification observer. BolusPro now
posts a single NotificationCenter event after each carb-entry save and
has zero external dependencies — making the feature shippable in
upstream LoopKit/Loop without DataLayer.
Pump pins fade over 10 days, sensor pins over 5 — clinical safe-reuse
windows for cannula vs filament tissue recovery. Past the threshold the
pin disappears from the body map (and placement-picker reference layer)
so users get an immediate visual signal that the site is safe to reuse.
Entry data is preserved in the Settings list for history.

- Theme.safeReuseDays(for:) returns 10 for pump, 5 for sensor
- ageColor / ageOpacity / shouldDisplayOnBodyMap take type parameter
- Placement-time recencyWeight uses the type-specific window
- Settings "Ready" badge uses type-specific threshold
- Map legend gradient sampled at 0/3/7/10 (pump window) — same ramp for both
When the user fine-tuned carbs via the AI confidence range slider, the
carbs circle still showed the AI's original estimate (e.g. slider at
56g but circle reading 62g). Carbs circle now reads from the bound
carbsQuantity, and calories adjusts proportionally (4 kcal/g) so the
displayed totals stay self-consistent. Fat / protein / fiber are
independent of the carb slider and stay at AI values.
Posts a com.loopkit.Loop.siteAtlasPlaced NotificationCenter event from
addEntry() with type / bodySide / zoneID (point-in-ellipse test against
the recommended zones) / replacementOfHidden flag. Decoupled — this
service has zero DataLayer deps; the matching observer lives in
DataLayer_Coordinator.

Enables real per-feature user counting on the dashboard's Feature
Adoption block alongside FoodFinder/LoopInsights/AutoPresets/BolusPro.
Posts a com.loopkit.Loop.graphDetailViewOpened NotificationCenter event
from .onAppear with hasGlucose / hasIOB / hasCOB / hasBolus / hasBasalRate
/ hasPreset / hasAutoPreset / hasHeartRate flags. Surfaces both raw
usage count AND which data series users actually inspect — useful signal
beyond a binary "did they open the popup".

Decoupled from DataLayer; the matching observer lives in
DataLayer_Coordinator.

Enables real per-feature user counting on the dashboard's Feature
Adoption block.
Adds two new event types so the dashboard's Feature Adoption block
gets real per-feature user counts (no stubbing):

- graphDetailViewOpened: payload of which data series the user inspected
  (hasGlucose, hasIOB, hasCOB, hasBolus, hasBasalRate, hasPreset,
  hasAutoPreset, hasHeartRate). Surfaces both raw count + what people
  actually look at when long-pressing the chart.

- siteAtlasPlaced: payload of type / bodySide / zoneID /
  replacementOfHidden. Counts site-rotation usage and identifies
  zone preferences + churn.

Both observers in DataLayer_Coordinator subscribe to NotificationCenter
events posted by the feature views — same decoupled pattern as BolusPro.
Consent category: .activityAndPresets (lightweight feature-usage signal).
Pure debounce was suppressing every reload until the user lifted their
finger — the popup followed the finger but the data inside never
refreshed mid-scrub. Replaced with a leading-edge throttle: first
update fires immediately, subsequent updates inside the 150ms window
coalesce into a trailing call so the popup settles on accurate data
when the user stops mid-window. Result: ~6 live data refreshes per
second during continuous scrub.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant