BusinessCard/ai_implementation.md

13 KiB
Raw Blame History

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.
  • Protocoloriented 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

  • Configuration/AppIdentifiers.swift — Centralized company identifiers:
    • companyIdentifier — Base identifier (e.g., "com.mbrucedogs")
    • Derived: bundleIdentifier, watchBundleIdentifier, appClipBundleIdentifier
    • Derived: appGroupIdentifier, cloudKitContainerIdentifier
    • App Clip: appClipDomain, appClipURL(recordName:)
    • Migration: Change companyIdentifier + update entitlements. 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, esMX, frCA.
  • 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.