# AI Implementation Context This file summarizes project-specific context, architecture, and conventions to speed up future AI work. ## Project Summary BusinessCard is a SwiftUI app for building and sharing digital business cards with QR codes. It includes iOS screens for cards, sharing, customization, contact tracking, and widget previews, plus a watchOS companion to show a default card QR code. ## Key Constraints - iOS 26+, watchOS 12+, Swift 6.2. - SwiftUI with `@Observable` classes and `@MainActor`. - Protocol‑oriented architecture is prioritized. - Clean Architecture with separation of concerns. - One public type per file, keep files under 300 lines. - No UIKit unless explicitly requested. - String Catalogs only (`.xcstrings`). - No magic numbers in views; use Bedrock's `Design` constants. - Uses **Bedrock** package for shared design system and utilities. ## Core Data Flow - `AppState` owns: - `CardStore` (cards and selection) - `ContactsStore` (contact list + search) - `ShareLinkService` (share URLs) - **SwiftData** with CloudKit for persistence and sync. - **WatchConnectivity** for iOS-Watch data sharing (NOT App Groups - see below). - Views read state via environment and render UI only. ### CRITICAL: WatchConnectivity vs App Groups **App Groups do NOT work for iPhone ↔ Apple Watch communication.** - App Groups only work between an app and its extensions on the SAME device - iPhone and Apple Watch are different devices with separate file systems - Use **WatchConnectivity** framework instead The `WatchConnectivityService` on iOS uses `updateApplicationContext` to push card data to the watch. The watch receives this data in `WatchConnectivityService` and updates `WatchCardStore`. ## Dependencies ### Bedrock Package Located at `/Frameworks/Bedrock` (local package). Provides: - `Design.Spacing`, `Design.CornerRadius`, `Design.Opacity`, etc. - `QRCodeGenerator` and `QRCodeImageView` for QR codes - Reusable settings components App-specific extensions are in `Design/DesignConstants.swift`: - `Design.CardSize` - card dimensions, avatar, QR sizes - `Design.Shadow.offsetNone` - zero offset extension - `Color.AppBackground`, `Color.CardPalette`, `Color.AppAccent`, `Color.AppText` ## Important Files ### Configuration (xcconfig) Company identifiers are centralized using xcconfig files for true single-source configuration: - `Configuration/Base.xcconfig` — Source of truth for all identifiers: - `COMPANY_IDENTIFIER` — Base identifier (e.g., "com.mbrucedogs") - `DEVELOPMENT_TEAM` — Apple Developer Team ID - Derived: `APP_BUNDLE_IDENTIFIER`, `WATCH_BUNDLE_IDENTIFIER`, `TESTS_BUNDLE_IDENTIFIER`, etc. - Entitlements: `APP_GROUP_IDENTIFIER`, `CLOUDKIT_CONTAINER_IDENTIFIER` - `Configuration/Debug.xcconfig` — Imports Base, adds debug-specific settings - `Configuration/Release.xcconfig` — Imports Base, adds release-specific settings - `Configuration/AppIdentifiers.swift` — Swift interface reading from Info.plist: - `appGroupIdentifier`, `cloudKitContainerIdentifier`, `appClipDomain` - `bundleIdentifier`, `watchBundleIdentifier`, `appClipBundleIdentifier` - `appClipURL(recordName:)` — Generates App Clip invocation URLs **Data flow**: `Base.xcconfig` → `project.pbxproj` → `Info.plist` → `AppIdentifiers.swift` **Migration**: Change `COMPANY_IDENTIFIER` and `DEVELOPMENT_TEAM` in `Base.xcconfig`. Everything else updates automatically. See `DevAccount-Migration.md`. ### Models - `Models/BusinessCard.swift` — SwiftData model with: - Name fields: prefix, firstName, middleName, lastName, maidenName, suffix, preferredName - Basic fields: role, company - Rich fields: pronouns, bio, headline, accreditations (comma-separated tags) - **Dynamic contact fields**: `@Relationship` to array of `ContactField` objects - Photos: `photoData` (profile), `coverPhotoData` (banner background), `logoData` (company logo) stored with `@Attribute(.externalStorage)` - Computed: `theme`, `layoutStyle`, `vCardPayload`, `orderedContactFields`, `fullName`, `vCardName` - Helper methods: `addContactField`, `removeContactField`, `reorderContactFields` **Name Properties (Single Source of Truth):** - `fullName` — Computed from individual name fields. Includes special formatting: preferred name in quotes, maiden name and pronouns in parentheses. THIS IS THE ONLY SOURCE for display names. - `vCardName` — Plain name for vCard export (no quotes or parentheses formatting). - ⚠️ There is NO stored `displayName` property. Always use `fullName` for display. - `Models/ContactField.swift` — SwiftData model for dynamic contact fields: - Properties: `typeId`, `value`, `title`, `orderIndex` - Relationship: `card` (inverse to BusinessCard) - Computed: `fieldType`, `displayName`, `iconImage()`, `iconColor`, `buildURL()` - Supports multiple fields of same type, drag-to-reorder - `Models/ContactFieldType.swift` — Struct defining 30+ field types: - Categories: contact, social, developer, messaging, payment, creator, scheduling, other - Properties: `id`, `displayName`, `systemImage`, `isCustomSymbol`, `iconColor` - Properties: `valueLabel`, `valuePlaceholder`, `titleSuggestions`, `keyboardType` - Method: `iconImage()` — returns correct Image (asset vs SF Symbol) - Method: `urlBuilder` — closure to build deep link URLs - Static instances: `.email`, `.phone`, `.linkedIn`, `.twitter`, `.instagram`, etc. - Uses custom assets from `Assets.xcassets/SocialSymbols/` - `Models/Contact.swift` — SwiftData model with: - Basic fields: name, role, company - Annotations: notes, tags (comma-separated), followUpDate, whereYouMet - Received cards: isReceivedCard, email, phone - Photo: `photoData` stored with `@Attribute(.externalStorage)` - editable via PhotosPicker in ContactDetailView and AddContactSheet - Computed: `tagList`, `hasFollowUp`, `isFollowUpOverdue` - Static: `fromVCard(_:)` parser - `Models/CardTheme.swift` — card theme palette - `Models/CardLayoutStyle.swift` — stacked/split/photo - `Models/AppTab.swift` — tab bar enum ### Protocols (POP) - `Protocols/BusinessCardProviding.swift` — card selection interface - `Protocols/ContactTracking.swift` — contact management interface - `Protocols/ShareLinkProviding.swift` — share URL generation interface ### State - `State/AppState.swift` — central state container - `State/CardStore.swift` — card CRUD, selection, watch sync - `State/ContactsStore.swift` — contacts, search, received cards ### Services - `Services/ShareLinkService.swift` — share URL helpers - `Services/VCardFileService.swift` — vCard file generation for sharing - `Services/WatchConnectivityService.swift` — WatchConnectivity sync to watch (uses `updateApplicationContext`) ### Views Main screens: - `Views/RootTabView.swift` — tabbed shell (3 tabs: My Cards, Contacts, Widgets) + floating share button - `Views/CardsHomeView.swift` — full-screen swipeable card view with edit button - `Views/ShareCardView.swift` — QR + share actions + track share (opened as sheet from floating button) - `Views/ContactsView.swift` — contact list with sections - `Views/WidgetsView.swift` — widget preview mockups Feature views: - `Views/BusinessCardView.swift` — card display with layouts - `Views/CardEditorView.swift` — create/edit cards with PhotosPicker for 3 image types (profile, cover, logo) - `Views/ContactDetailView.swift` — full contact view with annotations - `Views/QRScannerView.swift` — camera-based QR scanner - `Views/QRCodeView.swift` — QR code image generator Reusable components (in `Views/Components/`): - `AvatarBadgeView.swift` — circular avatar with photo or icon - `IconRowView.swift` — icon + text row for details - `LabelBadgeView.swift` — small badge labels - `ActionRowView.swift` — generic action row with chevron - `ContactFieldPickerView.swift` — grid picker for selecting contact field types - `ContactFieldsManagerView.swift` — orchestrates picker + added fields list - `AddedContactFieldsView.swift` — displays added fields with drag-to-reorder - `PhotoSourcePicker.swift` — generic photo source picker sheet (library, camera, remove) - `CameraCaptureView.swift` — UIImagePickerController wrapper for camera capture Sheets (in `Views/Sheets/`): - `RecordContactSheet.swift` — track share recipient - `ContactFieldEditorSheet.swift` — add/edit contact field with type-specific UI - `AddContactSheet.swift` — manually add a new contact - `PhotoCropperSheet.swift` — 2-step photo editor with pinch-to-zoom and square crop Small utilities: - `Views/EmptyStateView.swift` — empty state placeholder - `Views/PrimaryActionButton.swift` — styled action button ### Assets - `Assets.xcassets/SocialSymbols/` — custom brand icons as `.symbolset` files: - Social: linkedin, x-twitter, instagram, facebook, tiktok, threads, bluesky, mastodon, reddit, twitch - Developer: github.fill - Messaging: telegram, discord.fill, slack, matrix - Creator: patreon.fill, ko-fi **Icon Rendering:** - Custom symbols use `Image("symbolName")` (asset catalog) - SF Symbols use `Image(systemName: "symbolName")` - `ContactFieldType.isCustomSymbol` flag determines which to use - `ContactFieldType.iconImage()` returns the correct Image type ### Design + Localization - `Design/DesignConstants.swift` — extends Bedrock - `Resources/Localizable.xcstrings` — string catalog - `Localization/String+Localization.swift` — string helpers ### watchOS - `BusinessCardWatch Watch App/BusinessCardWatchApp.swift` — App entry point - `BusinessCardWatch Watch App/Views/WatchContentView.swift` — Shows default card QR code only (no picker) - `BusinessCardWatch Watch App/State/WatchCardStore.swift` — Receives cards from iPhone via WatchConnectivity - `BusinessCardWatch Watch App/Services/WatchConnectivityService.swift` — Receives `updateApplicationContext` from iPhone - `BusinessCardWatch Watch App/Models/WatchCard.swift` — Simplified card struct with `fullName`, `role`, `company`, `qrCodeData`, `isDefault` - `BusinessCardWatch Watch App/Design/WatchDesignConstants.swift` — Watch-specific design constants - `BusinessCardWatch Watch App/Resources/Localizable.xcstrings` **Watch Architecture Notes:** - Watch displays ONLY the default card (no card picker UI) - Default card is determined by iPhone's `isDefault` flag - QR codes are pre-generated on iPhone (CoreImage not available on watchOS) - Cards sync via `updateApplicationContext` (persists even when apps not running) - Bundle ID must be prefixed with iOS bundle ID (e.g., `com.mbrucedogs.BusinessCard.watchkitapp`) ## File Guidelines ### Size Limits - Main views: aim for under 300 lines - Extract reusable sub-views to `Components/` - Extract sheets/modals to `Sheets/` - Private structs in same file OK if under 50 lines ### Current File Sizes | File | Lines | Status | |------|-------|--------| | ContactFieldType | ~650 | 30+ field definitions, acceptable | | CardEditorView | ~520 | Complex form + field manager, acceptable | | BusinessCardView | ~320 | Clickable fields + legacy fallback, acceptable | | QRScannerView | ~310 | Camera + parsing, acceptable | | ShareCardView | ~235 | Good | | ContactDetailView | ~235 | Good | | ContactsView | ~220 | Good | | CardsHomeView | ~150 | Full-screen swipeable cards, good | | AddedContactFieldsView | ~135 | Drag-to-reorder, good | | ContactFieldPickerView | ~100 | Grid picker, good | | All others | <110 | Good | ## Localization - All user-facing strings are in `.xcstrings`. - Supported locales: en, es‑MX, fr‑CA. - Use `String.localized("Key")` for non-Text strings. ## Testing - `BusinessCardTests/BusinessCardTests.swift` covers: - vCard payload formatting - Card CRUD operations - Contact search and filtering - Social links detection - Contact notes/tags - Follow-up status - vCard parsing for received cards ## Known Stubs / TODOs - Apple Wallet and NFC flows are alert-only placeholders. - Share URLs are sample placeholders. - Widget previews are not WidgetKit extensions. - See `ROADMAP.md` for full feature status. ## Lessons Learned / Common Pitfalls ### Watch App Not Installing If `isWatchAppInstalled` returns `false` even with both apps running: 1. **Check "Embed Watch Content" build phase** in iOS target's Build Phases 2. **Ensure "Code Sign On Copy" is CHECKED** ← This is the #1 cause 3. Verify bundle ID is prefixed correctly 4. Clean build folder (⇧⌘K) and rebuild 5. On iPhone, open Watch app → verify app appears under "Installed" ### Simulator WatchConnectivity Issues WatchConnectivity is **unreliable on simulators**: - `isWatchAppInstalled` often returns `false` even when running - `isReachable` may be `false` even with both apps running - `updateApplicationContext` may fail with "counterpart not installed" **Solution**: Test real sync functionality on physical devices only. Use `#if targetEnvironment(simulator)` blocks for UI testing with sample data if needed. ### Single Source of Truth for Names The `BusinessCard` model uses individual name fields (prefix, firstName, middleName, lastName, etc.) and computes `fullName` dynamically. There is NO stored `displayName` property. - ✅ Use `card.fullName` for display everywhere - ✅ Use `card.vCardName` for vCard export - ❌ Never add a stored `displayName` property ### CoreImage on watchOS CoreImage is NOT available on watchOS. QR codes must be generated on iOS and sent to the watch as `Data`. ## If You Extend The App 1. Add new strings to the String Catalogs. 2. Use `Design.*` from Bedrock for spacing, opacity, etc. 3. Add app-specific constants to `DesignConstants.swift`. 4. Keep view logic UI-only; push business logic to state classes. 5. Prefer protocols for new capabilities. 6. Add unit tests for new model logic. 7. Update `ROADMAP.md` when adding features. 8. **Keep files under 300 lines** — extract components when needed. 9. **No duplicate code** — check for existing components first. 10. **One public type per file** — private helpers OK if small.