This reference is aimed at contributors who want to understand how FoodFinder is wired together inside Loop. It highlights the key modules, control flow, and extension points for adding new functionality.
┌──────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ UI & ViewModels│ ───▶ │ Services │ ───▶ │ External Providers │
└──────────────┘ └──────────────────┘ └──────────────────────┘
│ │ │
▼ ▼ ▼
CarbEntryView FoodSearchRouter OpenFoodFacts / USDA
FoodSearchBar ConfigurableAIService OpenAI / Claude / Gemini / BYO
FoodFinderSettings AIFoodAnalysis & Cache Device Camera / Barcode
AICameraView OpenFoodFactsService
FavoriteFoods views BarcodeScannerService
- UI Layer (SwiftUI) presents search controls, results, advanced analysis, and configuration screens.
- Services coordinate data fetching, AI prompt generation, caching, and provider selection.
- External Providers include public food databases and user-specified AI services accessed via HTTPS.
| Component | Path | Notes |
| CarbEntryView | Loop/Views/CarbEntryView.swift | Hosts the search bar, results list, advanced analysis foldout, and favorite food integration. |
| CarbEntryViewModel | Loop/View Models/CarbEntryViewModel.swift | Owns search text, barcode publisher, AI analysis results, caching of favorites, and absorption time logic. |
| FoodSearchBar & FoodSearchResultsView | Loop/Views | Provide search input plus animated feedback for loading, empty states, and errors. |
| AddEditFavoriteFoodView (+ VM) | Loop/Views / View Models | Handles saving favorites, enforcing truncation, and emoji mapping. |
| FavoriteFoodsView | Loop/Views/FavoriteFoodsView.swift | Lists stored favorites, supports reordering, editing, and deletion. |
| FoodFinderSettingsView | Loop/Views/FoodFinderSettingsView.swift | Summary screen with master toggle and quick status. |
| AISettingsView | Loop/Views/AISettingsView.swift | Full configuration UI for providers, API keys, analysis modes, and advanced features. |
| AICameraView | Loop/Views/AICameraView.swift | Guides users through capturing food photos, streams telemetry as analysis progresses. |
| VoiceSearchView | Loop/Views/VoiceSearchView.swift | Optional sheet driven by VoiceSearchService (not currently shown in the default UI but available for future use). |
| Service | Path | Responsibilities |
| ConfigurableAIService | Loop/Services/AIFoodAnalysis.swift | Stores provider choices for each search type, exposes API key/query bindings, resolves analysis mode, and pre-encodes images for reuse. |
| FoodSearchRouter | Loop/Services/FoodSearchRouter.swift | Routes text, barcode, and image requests to the configured provider with fallbacks (e.g., USDA → OpenFoodFacts). |
| AIFoodAnalysis | Loop/Services/AIFoodAnalysis.swift | Builds prompts, calls providers, parses JSON into AIFoodAnalysisResult, and manages caching (ImageAnalysisCache). Handles OpenAI, Claude, Gemini, and BYO flows including error translation. |
| OpenFoodFactsService | Loop/Managers/OpenFoodFactsService.swift | Wraps REST calls to OpenFoodFacts with retry logic and network tuning. |
| USDAFoodDataService | Loop/Services/AIFoodAnalysis.swift | Text-search helper using USDA FoodData Central when available. |
| BarcodeScannerService | Loop/Services/BarcodeScannerService.swift | Vision-based barcode detection and deduplication. |
| VoiceSearchService | Loop/Services/VoiceSearchService.swift | Speech recognition pipeline (authorization, audio capture, error handling). |
| EmojiThumbnailProvider & FavoriteFoodImageStore | Loop/Services | Generate emoji thumbnails and persist them for favorites. |
OpenFoodFactsProduct(Loop/Models/OpenFoodFactsModels.swift) – parsed database objects with helper properties.AIFoodAnalysisResult&FoodItemAnalysis(AIFoodAnalysis.swift) – normalized AI output consumed by UI.SearchProvider/SearchTypeenums – shared across services and settings.StoredFavoriteFood(LoopKit) – persisted favorite entries synced with UI.
- User input (typing, barcode, camera) updates
CarbEntryViewModel. - View model delegates to
FoodSearchRouterwhich chooses the appropriate provider, using cached data if available. - When AI analysis is required,
ConfigurableAIServiceprepares pre-encoded image data and prompt text viagetAnalysisPrompt(). AIFoodAnalysisissues async network requests using the selected provider client. It automatically falls back (e.g., GPT-5 → GPT-4o) when throttling or errors occur.- Responses are parsed into
AIFoodAnalysisResultwhere advanced fields are optional unlessadvancedDosingRecommendationsEnabledis true. - View model updates published properties; SwiftUI refreshes nutrition circles, advanced sections, favorites, and telemetry.
getAnalysisPrompt()returnsstandardAnalysisPrompt + mandatoryNoVagueBlockand appendsadvancedAnalysisRequirementswhen advanced dosing is enabled.- Advanced prompts demand JSON output with additional keys (
fat_protein_units,insulin_timing_recommendations,absorption_time_hours, etc.). - If you add or remove fields, update:
- Prompt constants in
AIFoodAnalysis.swift - Parsing logic in
parseOpenAIResponse,parseClaudeAnalysis,parseGeminiAnalysis - UI bindings in
CarbEntryViewandAICameraView - Documentation
- Prompt constants in
- Image cache:
ImageAnalysisCachestores AI results keyed by SHA-256 of the pre-encoded image + provider ID for five minutes. - Search cache:
CarbEntryViewModelcaches text search results for five minutes to avoid repeat HTTP calls. - Barcode dedupe:
BarcodeScannerServicethrottles identical scans to prevent repeated lookups. - Timeout wrapper:
withTimeoutForAnalysisguards long-running tasks (default 25–45 seconds based on network quality).
Error enums translate provider responses into localized strings so UI can show actionable messages (missing API key, 429 rate limits, parsing failures, etc.).
FoodSearchIntegrationTestsexercise text search, barcode lookups, and selection flows with mocked services (OpenFoodFactsService.configureMockResponses()).- Additional unit tests live under
LoopTests/BarcodeScannerTests.swiftandLoopTests/OpenFoodFactsTests.swift. FoodSearchBarandAICameraViewprint verbose debug logs (🔍,🤖) to help trace issues while developing. Use Console.app or Xcode’s debug area.- For manual testing, the Telemetry overlay in
AICameraViewdisplays each stage (image processing, upload, provider selection, etc.).
-
Adding a new provider
- Extend
SearchProviderand map defaults inConfigurableAIService. - Implement provider-specific network code (look at existing
OpenAIFoodAnalysisService/ClaudeFoodAnalysisService). - Update prompts if the provider requires different formatting.
- Wire through
AISettingsViewso users can supply API keys.
- Extend
-
Adjusting defaults
- Modify
ConfigurableAIServiceinit to set new default providers or analysis mode. - Update docs and release notes for users.
- Modify
-
Custom prompt tweaks
- Keep the shared
mandatoryNoVagueBlockaligned across providers. - Ensure JSON schema remains backwards compatible with existing UI fields.
- Keep the shared
-
Favorites enhancements
FavoriteFoodsViewModeltrims names and maps emojis. RespectmaxNameLengthconstants when surfacing new fields.
- Run the integration tests and manual camera/text searches with each provider.
- Verify advanced dosing JSON renders correctly when toggled on/off.
- Confirm FoodFinder Settings reflects new options and Save actually persists to
UserDefaults/Keychain. - Update this documentation folder and screenshots if the UI changes.
FoodFinder is tightly integrated with Loop’s carb entry workflow. Understanding the few core files above gives you everything you need to extend or debug the system without surprises. Happy hacking!