Skip to content

Add a redesigned interface with Now, Trends and Logbook tabs - #2615

Closed
Nutcasey wants to merge 6 commits into
LoopKit:devfrom
Nutcasey:loop-ui-redesign
Closed

Nutcasey wants to merge 6 commits into
LoopKit:devfrom
Nutcasey:loop-ui-redesign

Conversation

@Nutcasey

@Nutcasey Nutcasey commented Oct 26, 2026 •

Copy link
Copy Markdown

This adds a new interface to Loop: a tab bar with Now, Trends, Logbook and Settings. It is on by default, and Settings → New Interface switches back to the classic status screen without a relaunch.

This is ready for review. Feedback on the interface and how best to split the change is welcome.

How it fits into Loop

  • Now is still StatusTableViewController. In the new interface, its HUD and charts are hidden and two SwiftUI cells take their place. Bolus, carb entry, presets, settings, device screens, alerts, deeplinks and user-activity restoration all stay Loop's own code. Tiles, cards and pills open the screens Loop already has: IOB opens Insulin Delivery, COB opens Carb Absorption, "Next 3 hours" opens Predicted Glucose, and the device pills open the pump and CGM settings.
  • Trends covers a day, 7, 14, 30 or 90 days. It shows average glucose, GMI, insulin, carbs and boluses per day, time in range, an AGP-style daily pattern, insulin per day and Loop status. It can export a PDF report or a CSV with one row per day.
  • Logbook lists the last 7 days: boluses, carbs, temp basals, suspends and pump events, with filters. Carbs can be edited or deleted by swiping, following the classic editor's rules.
  • Data: the last 7 days come from Loop's stores (LOOP_LOCAL_CACHE_DURATION_DAYS). Older days come from Apple Health and respect the existing "observe other apps" flags. Basal totals use the units the pump reports as delivered, not the programmed rate, and gaps with no pump record count as no insulin. Read failures show an error state rather than zero.
  • Existing code touched:
    • LoopAppManager: installs and swaps the root, and applies the chosen appearance to the window.
    • DeeplinkManager: looks up the root on each link, because the root changes when the interface is switched.
    • StatusTableViewController: hosts the Now cells and captures self weakly in two existing Combine subscriptions, so switching interface releases the old controller. In the new interface it also gives its status row (No Recent Glucose, Tap to Resume, bolus progress, presets) a card's shape.
    • SettingsView: adds the toggle and the Appearance and App Icon row.
    • Widget extension: SystemStatusWidget and GlucoseLiveActivityConfiguration choose the redesigned views when New Interface is on. The timeline entry gains two fields. LoopCore's UserDefaults gains the shared setting.
  • New code is in Loop/Redesign/, with colour assets in DefaultAssets and LoopTests/Models/LoopHistoryMathTests.swift.

Screenshots (iOS simulator, Pump and CGM Simulators, generated history; the insulin figures aren't realistic)

Now Now, scrolled Day page Trends, 14 days Trends, 14 days, scrolled Trends, 90 days Logbook Logbook swipe New Interface setting Dark mode

Error states (added after review)

Pete asked what the error conditions look like, for example a faulted pod, a sensor in warmup, or signal loss from a device. I set each of these up in the simulator. Doing so found problems, which this update fixes:

  • A device problem could be off-screen. The status pills were in a row that scrolled sideways. With a long pump name and a message, the CGM pill was pushed past the edge. During warmup nothing on screen said the sensor was warming up. The pills now wrap onto a second line.
  • A problem is now a notice under the pills, in words, with the most serious first. It shows the device's own message and icon, says what it means for Loop ("Loop may not be giving insulin.", "Loop can't adjust insulin without readings."), and opens the device's settings. Critical is red, warning is amber, and informational states such as warmup are grey. A loop that hasn't completed for more than 15 minutes gets its own notice, which opens the last error.
  • Two-line plugin messages. G7 and Omnipod wrap their highlights onto two lines for the classic HUD ("Signal\nLoss"). They are now joined into one line.
  • "Open loop" when Loop had paused closed loop. Loop turns automation off while the CGM has no session or data. The pill said "Open loop", as if you had turned it off yourself. It now says "Closed loop paused" with an amber dot.
  • Loop's own status rows (No Recent Glucose · Tap to Add, Insulin Suspended · Tap to Resume, bolus progress and presets) were full-width UIKit rows that didn't match the new cards. They now take the card's shape. While delivery is suspended, the pump's "Insulin Suspended" notice is left out, because Loop's Resume row already says it and offers the action.
  • Loop's own alerts, such as Unable To Reach Pump, still appear over the new screen.

Pump fault Pump fault, dark Pump comms issue with Loop's alert Insulin suspended Sensor warmup CGM signal loss Sensor failed Loop not running

Before this update, for comparison: the warmup message is off-screen, and the pump fault pushes the CGM pill out of view.

Before: warmup pill off-screen Before: pump fault

How these were made. These are iOS simulator screenshots with the Pump and CGM Simulators.

  • Pump states use the simulator's own controls: Cause Pump Error stands in for a pod fault, Next Delivery Command Uncertain for a comms issue, Suspend Delivery for a suspension, and Error on Temp Basal for a stalled loop.
  • CGM states use a local hook, not part of this PR. The CGM Simulator can stop sending readings but has no warmup state. To get one, the hook makes the CGM Simulator return the G7 plugin's highlights word for word (Sensor Warmup, Signal Loss, Sensor Failed), with the CGM set to No Data.
  • Known simulator quirks: the overlapping dots on the chart come from a CGM Simulator backfill, and the trend arrow is the simulator's own.
  • Not tested on a real device: a faulted Omnipod 5 or a warming-up G7.

Appearance and app icons

Settings → Appearance and App Icon adds two choices:

  • Appearance: System, Day or Night. This sets the window's interface style, so it applies across Loop (both interfaces and every sheet) and changes without a relaunch. Night stays dark even while the iPhone is in light mode.
  • App Icon: Classic, Day and Night, Day, or Night.
    • Classic is the primary icon, unchanged, so a workspace's own icon override still applies.
    • Day and Night has a dark appearance variant (iOS 18 and later), so it follows the home screen's light or dark icons.
    • The new icons keep Loop's ring and add the blue reading dot from the Now screen: a light ground for Day, a midnight-blue ground for Night. The artwork was generated with an image model; I chose one of two takes for each.

Implementation:

  • The three new icons are alternate app icon sets in DerivedAssetsBase, listed in the Loop target's ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES.
  • iOS doesn't expose alternate icons as images, so 180 px preview image sets in DefaultAssets show them in the picker.
  • New code is in Loop/Redesign/AppearanceSettings.swift. LoopAppManager applies the style; SettingsView adds the row.

Tested:

  • On a real iPhone 14 Pro: iOS showed its "You have changed the icon for Loop" alert, using the night face because the phone was in dark mode. Day appearance applied live, and closed loop stayed on.
  • In the iOS 26.3 simulator, the icon changes, but iOS's own confirmation alert fails with "couldn't load upcall bundle principal class". After that, later changes are refused until the simulator restarts. So the picker trusts the icon iOS reports, not that error.
  • LoopTests: 323 pass.

Day and Night app icons

Settings row Appearance, day Appearance, night Now in Night while the system is light iPhone: icon changed Home screen with the new icon

Home screen widget, Lock Screen and Dynamic Island

The widget and the Live Activity now follow the same rules as the Now screen:

  • The number: a white SF Pro Rounded glucose number with a blue trend arrow. The number is never tinted.
  • Low and high: shown in words with a coloured dot ("Low", "Urgent low", "High"), at Now's thresholds. In the Dynamic Island, the change figure is replaced by the word, and the island's outline takes the colour.
  • Loop's state: a dot with words: "Closed loop · 16:12".
    • It shows the time of the last run rather than an age, because a widget isn't redrawn every minute.
    • After 16 minutes it changes to "Loop hasn't run for …".
  • Tiles and actions: sunken tiles and the redesign's quantity colours.
    • The medium widget has Now's Add carbs, Bolus, Pre-meal and Presets.
    • A pump or CGM problem replaces the widget's tiles, in words.
  • Lock Screen: the Now chart's style (blue readings, dashed prediction, target band). The configured bottom row becomes one-line tiles, within iOS's 160 pt limit.

How it fits in:

  • Follows New Interface: the old widget and Live Activity are unchanged for classic users. The app copies the setting into the app group at launch and on change, reloads the widget and redraws the Live Activity, so switching is immediate.
  • Shared design system: LoopDesign.swift and the redesign's colour sets are shared with the widget target.
  • Data: the widget entry gains the CGM highlight and COB from the existing extension context. GlucoseActivityAttributes is unchanged, so the Live Activity settings (chart, limits, bottom row) still apply.
  • New code: in Loop Widget Extension/Redesign/.
  • Strings: the widget's catalogue gains the redesign's strings in all 24 languages, reusing Loop's translations, plus a short "Forecast" tile label.

Tested:

  • Simulator (iPhone 14 Pro, iOS 26.3):
    • light and dark mode;
    • a high reading, a low reading and signal loss;
    • German and Arabic;
    • switching to classic and back.
    • These caught and fixed a missing tile fill in dark mode, truncated and clipped text in the expanded island, a Lock Screen layout over the height limit, and long German labels.
  • On a real iPhone 14 Pro with an Omnipod 5 and a Dexcom G7: installed and running, closed loop on, the Glooko data unchanged.

Before (classic) and after, each showing the home screen widgets, the expanded Dynamic Island and the Lock Screen:

Before: classic widget, Dynamic Island and Lock Screen After: light mode After: dark mode, high glucose After: German, high glucose After: Arabic, right to left

Localisation

The new interface is localised in all 24 of Loop's languages:

ar · cs · da · de · en · es · fi · fr · he · hu · it · ja · nb · nl · pl · pt-BR · ro · ru · sk · sv · tr · uk · vi · zh-Hans

It uses Loop's existing Localizable.xcstrings, so Lokalise picks the new strings up like any others.

Code

  • Coverage: every string shown or read aloud goes through NSLocalizedString with a translator comment. That covers text, VoiceOver labels and hints, alerts, chart legends and the PDF report.
  • Whole sentences: a sentence with values is one key with positional placeholders (Loop hasn't run for %1$@). No text is glued together in code.
  • Plurals: counts use plural variations (%lld days, %lld shorter gaps, Loop · %lld changes · tap to list). That gives Arabic six plural forms, Polish, Czech, Russian and Ukrainian four, and so on.
  • Numbers, dates and times: these follow the phone's locale.
    • LoopFormat.number replaces String(format:), so German shows "9,3".
    • Date ranges use DateIntervalFormatter.
    • Durations use DateComponentsFormatter.
    • Arabic gets Arabic-Indic digits and the Hijri calendar if the phone uses it.
    • Right-to-left layout works in Arabic and Hebrew.
  • Separate key for the Trends day: the one-day Trends period has its own key, Day (Trends period). "Day" is also the light appearance and icon name, and Arabic and Turkish need different words for "a day" and "daytime".
  • Long translations: the Now buttons wrap onto two lines, and tile labels and values shrink to fit.
  • Not translated, on purpose:
    • The CSV export stays English with . decimals, because it's data for spreadsheets.
    • mmol/L and mg/dL stay as they are, as elsewhere in Loop.

Translations

  • There are 213 new keys, plus the 23 languages they cover. The 14 existing keys the redesign reuses ("Bolus", %1$@ U, "Settings" and so on) keep their translations unchanged and gain any languages they were missing.
  • They follow each language's existing Loop terms, so "Bolus", "Closed Loop" and "IE"/"E"/"J"/"単位" match the rest of the app.
  • Every key was checked automatically: placeholder counts, literal % signs and the plural categories each language needs.
  • They were machine-assisted and haven't been reviewed by native speakers. Lokalise contributors will want to check them.
  • Where Loop's own glossary is still English (for example some Hebrew and Vietnamese terms), the new text matches it.

Testing

  • Every language was run in the simulator with -NSShowNonLocalizedStrings YES, which shows any string missing from the catalogue in capitals.
  • 11 screens were covered: Now (top and bottom), Trends (four positions), Logbook (two), Appearance and Settings.
  • No translated language showed a capitalised string. Only plugin text stays English, such as the simulator's "Sensor Warmup" and "alarm[Mock Pump Error]", because it comes from the device plugin.
  • LoopTests: 323 pass.

Now in each language

The Now screen in all 24 languages

Trends and Logbook in five languages, including right-to-left

Trends and Logbook in German, Arabic, Japanese, Russian and Hebrew

All 11 screens for every language are in screenshots/l10n/, one folder per language.

Testing

  • LoopTests: 323 tests pass in the iPhone 17 Pro simulator (iOS 26.3), and again on an iPhone 14 Pro simulator after the widget change. The test locale is en_US, because two existing bolus tests hard-code US clock strings. There are 17 new LoopHistoryMathTests, covering:
    • delivered-basal conversion;
    • the split between Health and Loop's own stores;
    • half-open bolus boundaries at midnight;
    • leaving the day in progress out of per-day averages;
    • daylight-saving days;
    • naming a day with no CGM readings.
  • Simulator checks:
    • Matches classic: after a 30 g meal and a 2.7 U bolus, glucose, trend, IOB, COB, net basal, Eventually, today's total and the target band match the classic screen.
    • Carb editing: a draft edit survives a data refresh, continues to the bolus screen and saves correctly.
    • Navigation: day stepping works, and the classic/new switch works with screens pushed.
    • Trends: per-day figures were checked against the CSV export.
  • On a real iPhone 14 Pro with an Omnipod 5, a Dexcom G7 and real Apple Health history:
    • Now, Logbook and the day page matched classic Loop's own screens, including a real meal and bolus;
    • Trends loaded every period, back to 90 days of Health data;
    • switching between classic and new kept closed loop on.
  • Not yet tested:
    • mg/dL on a device;
    • an iOS 17 runtime.

Known limitations and open questions

  • Translations need native review. All new text is localised in Loop's 24 languages (see Localisation), but the translations are machine-assisted and haven't been checked by native speakers.
  • On by default: happy to make it opt-in if that's preferred.
  • Loop status history: stored dosing decisions don't record whether closed loop was on, so the redesign keeps its own record of mode changes. Loop status covers only the time since that record began.
  • Pod and sensor time remaining aren't exposed by the plugins, so the device pills show the device name and any warning instead of "2d 14h".
  • The Lock Screen Live Activity doesn't show device problems. Its data has no highlight field, the same as the classic one.
  • Not yet checked: the widget in the tinted home screen style, and the Live Activity's Watch and CarPlay size.
  • Chart touch-and-hold needs iOS 18 or later. On iOS 17 the charts are static and the hint is hidden.
  • Size: the PR includes the interface, history provider and maths, localisation, artwork, widgets and tests. It could be split, for example into the history provider and maths, then Now, then Trends and Logbook.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QHnVPAQn8LyVAJrZEA3f2T

Codex review — 27 October 2026

Codex reviewed this PR using the code-reviewer-cx skill and fixed all outstanding high-confidence issues identified in that review in commit 76bbecb4:

  • CSV glucose values now use ASCII digits and a decimal point, preserving eight columns in decimal-comma locales. Displayed glucose remains localised.
  • Mode-history pruning retains the record establishing the state at the retention boundary.
  • Live Activity freshness uses the current time when rendered, rather than the saved glucose timestamp.
  • The redesigned widget and Live Activity show amber during Loop's ageing interval.

Verification: simulator build and navigation/sharing checks passed; CSV exports changed from nine-column rows to eight-column rows under de_DE. Both new regression tests failed against the original code and passed with the fixes under en_DE. Full LoopTests: 325 passed, zero failures, Glooko iPhone 14 Pro simulator, iOS 26.3.1, en_US. A status probe checked fresh, ageing and stale colours. Changed localisation entries have no format-placeholder mismatches.

No further high-confidence blockers found. Ready for human review. The existing real-device and translation-review limitations still apply; these fixes were verified in the simulator.

…dling

Build Now, Trends and Logbook around Loop's existing controllers and stores.
Keep delivered quantities and history partitions accurate, preserve editing
drafts across updates, propagate load failures and share chart averages.
Per-day Trends figures leave out the day in progress and share insulin's
days, the day chart names a day without readings, and the Logbook carb
editor receives Loop updates on the main thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Nutcasey
Nutcasey marked this pull request as ready for review October 26, 2026 16:59
Nutcasey and others added 3 commits October 27, 2026 08:24
Driving the error states in the simulator (pump fault, comms issue,
suspension, sensor warmup, signal loss, sensor failed) showed that a
device problem could be off-screen: the status pills scrolled sideways,
so during warmup the CGM pill was past the edge and nothing visible said
so.

- Status pills wrap instead of scrolling.
- A device highlight, or a loop that hasn't completed for 15 minutes,
  gets a notice under the pills with the device's message and icon, what
  it means for Loop, and a tap through to the device; most serious first.
- Two-line plugin highlights ("Signal\nLoss") are joined.
- When Loop pauses closed loop (no CGM session or data), the pill says
  "Closed loop paused" instead of "Open loop".
- Loop's status rows (No Recent Glucose, Tap to Resume, bolus progress,
  presets) take the card shape in the new interface; the non-critical
  pump notice is left out while the Resume row shows.

LoopTests: 323 pass on the iPhone 17 Pro simulator (iOS 26.3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QHnVPAQn8LyVAJrZEA3f2T
Settings gets an "Appearance and App Icon" screen:

- Appearance: System, Day or Night. It sets the window's interface
  style, so it applies to the whole app (new and classic interface,
  sheets included) and takes effect without a relaunch.
- App Icon: Classic (the primary icon, unchanged), Day, Night, and Day
  and Night, whose dark appearance variant follows the home screen's
  light or dark icons. Day, Night and Day and Night are alternate icon
  sets in DerivedAssetsBase, listed in the Loop target's
  ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES; small preview image
  sets show them in the picker because iOS doesn't expose alternate
  icons as images.

The new icons keep Loop's ring and add the blue reading dot from the
Now screen, on a light ground for Day and a midnight-blue one for
Night. The artwork was generated with GPT image generation and chosen
from two takes each.

iOS can report an error from its "icon changed" alert even though the
icon changed (the iOS 26.3 simulator does this every time), so the
picker trusts the icon iOS reports afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QHnVPAQn8LyVAJrZEA3f2T
Every string the new interface shows or speaks now goes through
NSLocalizedString with a translator comment, and Localizable.xcstrings
gains 213 keys with translations in all 23 of Loop's other languages
(ar cs da de es fi fr he hu it ja nb nl pl pt-BR ro ru sk sv tr uk vi
zh-Hans). Fourteen existing keys the redesign reuses ("Bolus", "%1$@ U",
"Settings", ...) keep their translations and gain the languages they
were missing.

- Sentences with values are whole keys with positional placeholders,
  and counts ("%lld days", "%lld shorter gaps", "Loop · %lld changes")
  use plural variations, so no text is glued together in code.
- Numbers, dates, ranges and durations follow the user's locale:
  LoopFormat.number replaces String(format:), date ranges use
  DateIntervalFormatter and durations DateComponentsFormatter.
- The Trends one-day period has its own key, "Day (Trends period)",
  because "Day" is also the light appearance and icon name and some
  languages need different words for the two.
- The Now action buttons wrap to two lines and tile labels scale down,
  so longer languages don't truncate.
- The CSV export stays English with '.' decimals on purpose; it is
  data for spreadsheets, not display text.

Translations follow Loop's existing terms per language and were checked
for placeholder and plural-category integrity. Each language was run
through the simulator with NSShowNonLocalizedStrings. They have not
been reviewed by native speakers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QHnVPAQn8LyVAJrZEA3f2T
@torstenlemke

Copy link
Copy Markdown

love this +1

Nutcasey and others added 2 commits October 27, 2026 15:49
…esign

The home screen widget and the Live Activity now follow the redesign's
rules from the Now screen: a white SF Pro Rounded glucose number with a
blue trend arrow, low and high said in words rather than by tinting the
number, Loop's state as a dot with words, sunken tiles, and device
problems written out.

- Widget: the Now hero with COB and Forecast tiles; the medium size adds
  Add carbs, Bolus, Pre-meal and Presets in the Now screen's styles.
- Lock Screen: hero, the Now chart's style (blue readings, dashed
  prediction, target band) and the configured bottom row as one-line
  tiles, within the 160 pt limit.
- Dynamic Island: white number and blue arrow, the change or "Low"/"High"
  in words with a loop dot, a keyline tinted for low and high, and an
  expanded view with a full-width status line and the chart.

They follow the New Interface setting: the app mirrors it into the app
group, reloads widget timelines and redraws the Live Activity when it
changes, so classic users keep the classic widget and Live Activity.
LoopDesign.swift and the redesign's colour sets are shared with the
widget target. The widget entry gains the CGM highlight and COB from the
existing extension context.

The widget catalogue gains the redesign's strings in all 24 languages,
reusing Loop's translations, plus a short "Forecast" tile label. The
Arabic "in 5 min" now uses an Arabic-Indic digit in both catalogues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QHnVPAQn8LyVAJrZEA3f2T

@Nutcasey Nutcasey left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex reviewed PR #2615 against dev using the code-reviewer-cx skill. I fixed all outstanding high-confidence issues identified in this review and pushed commit 76bbecb4c03a02de3259d95d3d487f49dfc354f8.

Important findings, now fixed:

  1. CSV column corruption — confidence 89/100. Loop/Redesign/TrendsScreen.swift:204: localised glucose such as 7,3 became two CSV fields. Export now uses POSIX numeric formatting. The actual share flow produced nine-column rows before the fix and eight-column rows afterwards in de_DE.
  2. Lost mode-history coverage — confidence 88/100. Loop/Redesign/LoopHistoryProvider.swift:425: pruning could remove the record establishing the state before a new switch after more than 100 days. Retaining the preceding boundary record preserves recent coverage.
  3. Frozen Live Activity freshness — confidence 89/100. Loop Widget Extension/Redesign/RedesignLiveActivity.swift:38: the saved glucose timestamp was used as “now”. Freshness now uses the current time when rendered, matching the classic implementation's clock.
  4. Missing ageing warning — confidence 87/100. Loop Widget Extension/Redesign/WidgetDesign.swift:90: the redesigned status stayed green through the ageing interval. It now becomes amber after the fresh interval.

Verification:

  • Simulator build and navigation/share-flow checks passed.
  • Both new regression tests failed against the original implementations for the expected reasons, then passed with the fixes under en_DE.
  • Full LoopTests: 325 passed, zero failures, Glooko iPhone 14 Pro simulator, iOS 26.3.1, en_US.
  • Focused status probe: fresh at 2 minutes, amber at 8 and 20 minutes.
  • Changed localisation entries: zero format-placeholder mismatches; git diff --check passed.

No further high-confidence blockers found. Codex has reviewed this PR and fixed the outstanding issues it identified; it is ready for human review. The PR was already open and non-draft; I have updated its description to reflect that. These fixes were not deployed to the physical phone.

@Nutcasey

Copy link
Copy Markdown
Author

Closing in favour of #2621, which carries this redesign onto next-dev. It has been rebased onto the current next-dev, and that's where review should continue. The screenshots and notes here still describe the interface.

🤖 Generated with Claude Code

@Nutcasey Nutcasey closed this Oct 28, 2026
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.

2 participants