Skip to content

Commit bbe20aa

Browse files
Reorder: insert features after Therapy Settings, not before ForEach
Anchor on "Diabetes Treatment" instead of the ForEach line so our features (FoodFinder, LoopInsights, AutoPresets) appear right after Therapy Settings. If L&L Profiles is installed, it ends up below our features instead of above. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2e69b0d commit bbe20aa

2 files changed

Lines changed: 145 additions & 23 deletions

File tree

Scripts/install_features.sh

Lines changed: 144 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ NEW_FILES=(
147147

148148
# Modified files to patch via git diff | git apply --3way
149149
# Excludes: project.pbxproj (handled by Python script), SettingsView.swift (anchor-based),
150+
# LoopDataManager.swift (anchor-based — L&L Customizations modify this file heavily),
150151
# and Localizable.xcstrings (direct checkout — too large for 3-way merge on JSON)
151152
PATCH_FILES=(
152-
"Loop/Managers/LoopDataManager.swift"
153153
"Loop/View Controllers/StatusTableViewController.swift"
154154
"Loop/View Models/AddEditFavoriteFoodViewModel.swift"
155155
"Loop/View Models/CarbEntryViewModel.swift"
@@ -222,8 +222,8 @@ validate_environment() {
222222
die "SettingsView.swift not found at expected path."
223223
fi
224224

225-
if ! grep -q 'ForEach(pluginMenuItems\.filter {\$0\.section == \.configuration})' "$settings_file"; then
226-
die "Anchor not found in SettingsView.swift: ForEach(pluginMenuItems.filter {\$0.section == .configuration})
225+
if ! grep -q 'Diabetes Treatment' "$settings_file"; then
226+
die "Anchor not found in SettingsView.swift: Diabetes Treatment
227227
Your Loop version may be incompatible."
228228
fi
229229

@@ -426,8 +426,10 @@ with open(settings_path, "r") as f:
426426
427427
lines = content.split("\n")
428428
429-
# ─── Anchor 1: Insert feature rows BEFORE the ForEach(pluginMenuItems.filter configuration) ───
430-
# This is inside configurationSection. We insert FoodFinder, LoopInsights, and AutoPresets rows.
429+
# ─── Anchor 1: Insert feature rows AFTER the Therapy Settings button ───
430+
# We anchor on "Diabetes Treatment" (the Therapy Settings descriptive text) so our
431+
# features appear right after Therapy Settings. If L&L Profiles is installed, it
432+
# inserts before the ForEach — so Profiles ends up BELOW our features.
431433
432434
FEATURE_ROWS = """
433435
foodFinderSettingsRow
@@ -445,7 +447,7 @@ FEATURE_ROWS = """
445447
}
446448
"""
447449
448-
anchor1 = 'ForEach(pluginMenuItems.filter {$0.section == .configuration})'
450+
anchor1 = 'Diabetes Treatment'
449451
anchor1_idx = None
450452
for i, line in enumerate(lines):
451453
if anchor1 in line:
@@ -456,11 +458,12 @@ if anchor1_idx is None:
456458
print(f"ERROR: Anchor 1 not found: {anchor1}", file=sys.stderr)
457459
sys.exit(1)
458460
459-
# Insert the feature rows BEFORE the ForEach line
461+
# Insert the feature rows AFTER the Therapy Settings descriptive text line
460462
feature_lines = FEATURE_ROWS.rstrip("\n").split("\n")
463+
insert_at = anchor1_idx + 2 # after the NavigationLink closing brace (line after "Diabetes Treatment")
461464
for j, fl in enumerate(feature_lines):
462-
lines.insert(anchor1_idx + j, fl)
463-
print(f" Inserted {len(feature_lines)} lines before ForEach anchor (line {anchor1_idx + 1})")
465+
lines.insert(insert_at + j, fl)
466+
print(f" Inserted {len(feature_lines)} lines after Therapy Settings (line {anchor1_idx + 1})")
464467
465468
# ─── Anchor 2: Insert computed properties BEFORE "private var cgmChoices:" ───
466469
@@ -479,18 +482,16 @@ COMPUTED_PROPS = """
479482
}
480483
481484
private var loopInsightsSection: some View {
482-
Section {
483-
NavigationLink(destination: LoopInsights_SettingsView(dataStoresProvider: viewModel.loopInsightsDataStores)) {
484-
LargeButton(action: {},
485-
includeArrow: false,
486-
imageView: Image(systemName: "brain.head.profile")
487-
.resizable()
488-
.aspectRatio(contentMode: .fit)
489-
.foregroundColor(Color(red: 26/255, green: 138/255, blue: 158/255))
490-
.frame(width: 30),
491-
label: NSLocalizedString("LoopInsights", comment: "LoopInsights settings button"),
492-
descriptiveText: NSLocalizedString("AI-powered therapy settings analysis", comment: "LoopInsights settings descriptive text"))
493-
}
485+
NavigationLink(destination: LoopInsights_SettingsView(dataStoresProvider: viewModel.loopInsightsDataStores)) {
486+
LargeButton(action: {},
487+
includeArrow: false,
488+
imageView: Image(systemName: "brain.head.profile")
489+
.resizable()
490+
.aspectRatio(contentMode: .fit)
491+
.foregroundColor(Color(red: 26/255, green: 138/255, blue: 158/255))
492+
.frame(width: 30),
493+
label: NSLocalizedString("LoopInsights", comment: "LoopInsights settings button"),
494+
descriptiveText: NSLocalizedString("AI-powered therapy settings analysis", comment: "LoopInsights settings descriptive text"))
494495
}
495496
}
496497
@@ -528,6 +529,126 @@ PYTHON_SCRIPT
528529
fi
529530
}
530531

532+
# ─── Phase 6b: Patch LoopDataManager.swift (Anchor-Based) ────────────────────
533+
#
534+
# L&L Customizations heavily modify LoopDataManager.swift (Negative Insulin Damper,
535+
# function signature changes, etc.), so git apply --3way fails silently.
536+
# Instead, we use anchor-based insertion like SettingsView.swift.
537+
538+
patch_loop_data_manager() {
539+
header "Phase 6b: Patching LoopDataManager.swift (anchor-based)"
540+
541+
local ldm_file="Loop/Loop/Managers/LoopDataManager.swift"
542+
543+
if [[ ! -f "$ldm_file" ]]; then
544+
die "LoopDataManager.swift not found at: $ldm_file"
545+
fi
546+
547+
# Skip if already patched
548+
if grep -q "AutoPresetsCoordinator" "$ldm_file"; then
549+
info "LoopDataManager.swift already contains AutoPresets code — skipping."
550+
return 0
551+
fi
552+
553+
python3 - "$ldm_file" << 'PYTHON_SCRIPT'
554+
import sys
555+
556+
ldm_path = sys.argv[1]
557+
558+
with open(ldm_path, "r") as f:
559+
content = f.read()
560+
561+
lines = content.split("\n")
562+
563+
# ─── Anchor 1: Insert delegate setup after "self.trustedTimeOffset = trustedTimeOffset" ───
564+
# This is in the init method. The delegate line goes right after this assignment,
565+
# before the LiveActivity setup.
566+
567+
DELEGATE_SETUP = """\
568+
569+
// Set up AutoPresets coordinator delegate
570+
AutoPresetsCoordinator.shared.delegate = self
571+
"""
572+
573+
anchor1 = "self.trustedTimeOffset = trustedTimeOffset"
574+
anchor1_idx = None
575+
for i, line in enumerate(lines):
576+
if anchor1 in line:
577+
anchor1_idx = i
578+
break
579+
580+
if anchor1_idx is None:
581+
print(f"ERROR: Anchor not found: {anchor1}", file=sys.stderr)
582+
sys.exit(1)
583+
584+
delegate_lines = DELEGATE_SETUP.rstrip("\n").split("\n")
585+
insert_at = anchor1_idx + 1
586+
for j, dl in enumerate(delegate_lines):
587+
lines.insert(insert_at + j, dl)
588+
print(f" Inserted delegate setup ({len(delegate_lines)} lines) after line {anchor1_idx + 1}")
589+
590+
# ─── Anchor 2: Append AutoPresetsDelegate extension at end of file ───
591+
# We find the very last closing brace of the file and append after it.
592+
593+
DELEGATE_EXTENSION = """
594+
// MARK: - AutoPresetsDelegate
595+
596+
extension LoopDataManager: AutoPresetsDelegate {
597+
598+
func autoPresets(_ coordinator: AutoPresetsCoordinator,
599+
shouldActivatePreset preset: TemporaryScheduleOverridePreset) {
600+
logger.default("AutoPresets activating preset: %{public}@", preset.name)
601+
602+
mutateSettings { settings in
603+
settings.scheduleOverride = preset.createOverride(enactTrigger: .local)
604+
}
605+
}
606+
607+
func autoPresets(_ coordinator: AutoPresetsCoordinator,
608+
shouldDeactivatePreset preset: TemporaryScheduleOverridePreset) {
609+
guard let currentOverride = settings.scheduleOverride,
610+
case let .preset(currentPreset) = currentOverride.context,
611+
currentPreset.id == preset.id
612+
else {
613+
return
614+
}
615+
616+
logger.default("AutoPresets deactivating preset: %{public}@", preset.name)
617+
618+
mutateSettings { settings in
619+
settings.scheduleOverride = nil
620+
}
621+
}
622+
623+
func autoPresetsAvailablePresets(_ coordinator: AutoPresetsCoordinator) -> [TemporaryScheduleOverridePreset] {
624+
settings.overridePresets
625+
}
626+
627+
func autoPresetsCurrentOverride(_ coordinator: AutoPresetsCoordinator) -> TemporaryScheduleOverride? {
628+
settings.scheduleOverride
629+
}
630+
}
631+
"""
632+
633+
extension_lines = DELEGATE_EXTENSION.split("\n")
634+
lines.extend(extension_lines)
635+
print(f" Appended AutoPresetsDelegate extension ({len(extension_lines)} lines) at end of file")
636+
637+
# Write back
638+
with open(ldm_path, "w") as f:
639+
f.write("\n".join(lines))
640+
641+
print(" LoopDataManager.swift patched successfully.")
642+
PYTHON_SCRIPT
643+
644+
if [[ $? -eq 0 ]]; then
645+
success "LoopDataManager.swift patched with AutoPresets delegate"
646+
else
647+
error "Failed to patch LoopDataManager.swift"
648+
return 1
649+
fi
650+
}
651+
531652
# ─── Phase 7: Update project.pbxproj ─────────────────────────────────────────
532653

533654
update_pbxproj() {
@@ -669,7 +790,7 @@ cleanup() {
669790
echo -e " ${BOLD}Next steps:${NC}"
670791
echo " 1. Open LoopWorkspace.xcworkspace in Xcode"
671792
echo " 2. Build and run (Cmd+R)"
672-
echo " 3. In the Loop app: Settings → enable FoodFinder / LoopInsights"
793+
echo " 3. In Loop > Settings > Enable AutoPresets / FoodFinder / LoopInsights"
673794
echo " 4. Enter your AI API key in FoodFinder Settings"
674795
echo ""
675796
echo -e " ${BOLD}To uninstall:${NC}"
@@ -774,6 +895,7 @@ main() {
774895
install_new_files
775896
patch_modified_files
776897
patch_settings_view
898+
patch_loop_data_manager
777899
update_pbxproj
778900
validate_installation
779901
cleanup

0 commit comments

Comments
 (0)