SmartStore AI — Phase 4 Implementation Guide

SwiftUI MVP Chat UI

What got built

ios/SmartStoreAI/
├── APIClient.swift       — networking layer, calls POST /ask
├── ChatViewModel.swift    — @MainActor state owner, no networking in the view
├── ChatView.swift          — the actual chat screen (message bubbles, input, sources shown per answer)
└── SmartStoreAIApp.swift   — updated to launch ChatView instead of Phase 0's placeholder

Key design decisions

Networking lives in APIClient, state lives in ChatViewModel, layout lives in ChatView — strictly separated. This isn't just "clean architecture" for its own sake: Phase 5 adds an auth header to every request, and Phase 8 adds streaming — both changes touch APIClient and ChatViewModel only. ChatView doesn't change for either, because it was never doing networking in the first place.

storeId is hardcoded for now, passed into ChatViewModel's initializer. This is intentionally not solved properly yet — Phase 5 replaces the hardcoded value with the authenticated user's real store context. Solving it "properly" now, before auth exists, would mean building it twice.

Sources are displayed under each answer, using the sources array Phase 3's /ask endpoint already returns. This is a small thing worth not skipping: showing which product the answer is grounded in is the same traceability principle from Volume 2, Ch.10 of the bootcamp, made visible in the actual UI, not just present in the API response and then thrown away.

Honest limitation

This Swift code was not compiled or run — there's no Xcode/Swift toolchain available in this sandbox (it's Linux-only). The code follows verified-correct SwiftUI/Swift Concurrency patterns (the same @MainActor, async/await, @StateObject patterns used correctly throughout Phase 0), but your first build in Xcode is the real verification step here, the same caveat as Phase 0.

To actually test this: 1. Drag the three new files into your Xcode project (alongside Phase 0's files) 2. Make sure docker compose up is running, and Phase 1's seed data + Phase 2's ingestion have run 3. Replace the hardcoded storeId in SmartStoreAIApp.swift with a real store UUID from your seeded data (SELECT id FROM stores;) 4. Run on the simulator, ask "where's the olive oil?"

What's next

Phase 5 — Authentication replaces the hardcoded storeId with real Firebase Auth, on both the iOS side (sign-in, attaching a token to every request) and the backend side (verifying that token before trusting anything about who's asking).