BusinessCard/Agents.md

493 lines
19 KiB
Markdown

# Agent Guide for Swift and SwiftUI
This repository contains an Xcode project written with Swift and SwiftUI. Please follow the guidelines below so that the development experience is built on modern, safe API usage.
## Additional Context Files (Read First)
**These files are kept in sync and MUST be updated together when making changes:**
- `README.md` — Product scope, features, and project structure
- `ai_implementation.md` — AI implementation context and architecture notes
See **Documentation Instructions** section for mandatory sync requirements.
## Role
You are a **Senior iOS Engineer**, specializing in SwiftUI, SwiftData, and related frameworks. Your code must always adhere to Apple's Human Interface Guidelines and App Review guidelines.
## Core Instructions
- Target iOS 26.0 or later. (Yes, it definitely exists.)
- Swift 6.2 or later, using modern Swift concurrency.
- SwiftUI backed up by `@Observable` classes for shared data.
- **Prioritize Protocol-Oriented Programming (POP)** for reusability and testability.
- **Follow Clean Architecture principles** for maintainable, testable code.
- Do not introduce third-party frameworks without asking first.
- Avoid UIKit unless requested.
## Clean Architecture
**Separation of concerns is mandatory.** Code should be organized into distinct layers with clear responsibilities and dependencies flowing inward.
### File Organization Principles
1. **One public type per file**: Each file should contain exactly one public struct, class, or enum. Private supporting types may be included if they are small and only used by the main type.
2. **Keep files lean**: Aim for files under 300 lines. If a file exceeds this:
- Extract reusable sub-views into separate files in a `Components/` folder
- Extract sheets/modals into a `Sheets/` folder
- Move complex logic into dedicated types
3. **No duplicate code**: Before writing new code, search for existing implementations. Extract common patterns into reusable components.
4. **Logical grouping**: Organize files by feature, not by type:
```
Feature/
├── Views/
│ ├── FeatureView.swift
│ ├── Components/
│ │ ├── FeatureRowView.swift
│ │ └── FeatureHeaderView.swift
│ └── Sheets/
│ └── FeatureEditSheet.swift
├── Models/
│ └── FeatureModel.swift
└── State/
└── FeatureStore.swift
```
### Layer Responsibilities
| Layer | Contains | Depends On |
|-------|----------|------------|
| **Views** | SwiftUI views, UI components | State, Models |
| **State** | `@Observable` stores, view models | Models, Services |
| **Services** | Business logic, networking, persistence | Models |
| **Models** | Data types, entities, DTOs | Nothing |
| **Protocols** | Interfaces for services and stores | Models |
### Architecture Rules
1. **Views are dumb renderers**: No business logic in views. Views read state and call methods.
2. **State holds business logic**: All computations, validations, and data transformations.
3. **Services are stateless**: Pure functions where possible. Injected via protocols.
4. **Models are simple**: Plain data types. No dependencies on UI or services.
### Example Structure
```
App/
├── Design/ # Design constants, colors, typography
├── Localization/ # String helpers
├── Models/ # Data models (SwiftData, plain structs)
├── Protocols/ # Protocol definitions for DI
├── Services/ # Business logic, API clients, persistence
├── State/ # Observable stores, app state
└── Views/
├── Components/ # Reusable UI components
├── Sheets/ # Modal presentations
└── [Feature]/ # Feature-specific views
```
## Protocol-Oriented Programming (POP)
**Protocol-first architecture is a priority.** When designing new features, always think about protocols and composition before concrete implementations.
### When Architecting New Code
1. **Start with the protocol**: Before writing a concrete type, ask "What capability am I defining?" and express it as a protocol.
2. **Identify shared behavior**: If multiple types will need similar functionality, define a protocol first.
3. **Use protocol extensions for defaults**: Provide sensible default implementations to reduce boilerplate.
4. **Prefer composition over inheritance**: Combine multiple protocols rather than building deep class hierarchies.
### When Reviewing Existing Code
1. **Look for duplicated patterns**: Similar logic across files is a candidate for protocol extraction.
2. **Identify common interfaces**: Types that expose similar properties/methods should conform to a shared protocol.
3. **Check before implementing**: Search for existing protocols that could be adopted or extended.
4. **Propose refactors proactively**: When you spot an opportunity to extract a protocol, mention it.
### Protocol Design Guidelines
- **Name protocols for capabilities**: Use `-able`, `-ing`, or `-Provider` suffixes (e.g., `Shareable`, `DataProviding`, `Persistable`).
- **Keep protocols focused**: Each protocol should represent one capability (Interface Segregation Principle).
- **Use associated types sparingly**: Prefer concrete types or generics at the call site when possible.
- **Constrain to `AnyObject` only when needed**: Prefer value semantics unless reference semantics are required.
### Benefits
- **Reusability**: Shared protocols work across features
- **Testability**: Mock types can conform to protocols for unit testing
- **Flexibility**: New features can adopt existing protocols immediately
- **Maintainability**: Fix a bug in a protocol extension, fix it everywhere
- **Discoverability**: Protocols document the expected interface clearly
## View/State Separation (MVVM-lite)
**Views should be "dumb" renderers.** All business logic belongs in stores or dedicated view models.
### What Belongs in State/Store
- **Business logic**: Calculations, validations, rules
- **Computed properties based on data**: Hints, recommendations, derived values
- **State checks**: `canSubmit`, `isLoading`, `hasError`
- **Data transformations**: Filtering, sorting, aggregations
### What is Acceptable in Views
- **Pure UI layout logic**: Adaptive layouts based on size class
- **Visual styling**: Color selection based on state
- **@ViewBuilder sub-views**: Breaking up complex layouts (keep in same file if small)
- **Accessibility labels**: Combining data into accessible descriptions
### Example
```swift
// ❌ BAD - Business logic in view
struct MyView: View {
@Bindable var state: FeatureState
private var isValid: Bool {
!state.name.isEmpty && state.email.contains("@")
}
}
// ✅ GOOD - Logic in State, view just reads
// In FeatureState:
var isValid: Bool {
!name.isEmpty && email.contains("@")
}
// In View:
Button("Save") { state.save() }
.disabled(!state.isValid)
```
## Swift Instructions
- Always mark `@Observable` classes with `@MainActor`.
- Assume strict Swift concurrency rules are being applied.
- Prefer Swift-native alternatives to Foundation methods where they exist.
- Prefer modern Foundation API (e.g., `URL.documentsDirectory`, `appending(path:)`).
- Never use C-style number formatting; use `format:` modifiers instead.
- Prefer static member lookup to struct instances (`.circle` not `Circle()`).
- Never use old-style GCD; use modern Swift concurrency.
- Filtering text based on user-input must use `localizedStandardContains()`.
- Avoid force unwraps and force `try` unless unrecoverable.
## SwiftUI Instructions
- Always use `foregroundStyle()` instead of `foregroundColor()`.
- Always use `clipShape(.rect(cornerRadius:))` instead of `cornerRadius()`.
- Always use the `Tab` API instead of `tabItem()`.
- Never use `ObservableObject`; always prefer `@Observable` classes.
- Never use `onChange()` in its 1-parameter variant.
- Never use `onTapGesture()` unless you need tap location/count; use `Button`.
- Never use `Task.sleep(nanoseconds:)`; use `Task.sleep(for:)`.
- Never use `UIScreen.main.bounds` to read available space.
- Do not break views up using computed properties; extract into new `View` structs.
- Do not force specific font sizes; prefer Dynamic Type.
- Use `NavigationStack` with `navigationDestination(for:)`.
- If using an image for a button label, always specify text alongside.
- Prefer `ImageRenderer` to `UIGraphicsImageRenderer`.
- Use `bold()` instead of `fontWeight(.bold)`.
- Avoid `GeometryReader` if newer alternatives work (e.g., `containerRelativeFrame()`).
- When enumerating in `ForEach`, don't convert to Array first.
- Hide scroll indicators with `.scrollIndicators(.hidden)`.
- Avoid `AnyView` unless absolutely required.
- **Never use raw numeric literals** for padding, spacing, opacity, etc.—use Design constants.
- **Never use inline colors**—define all colors with semantic names.
- Avoid UIKit colors in SwiftUI code.
## watchOS Development (CRITICAL)
**Read this entire section before implementing any watch functionality.**
### Creating a Watch Target
When adding a watchOS target to an existing iOS app:
1. **File → New → Target → "Watch App for watchOS"**
2. Choose **"Watch App for Existing iOS App"** (NOT standalone)
3. Name it appropriately (e.g., `AppNameWatch`)
4. Xcode creates a folder like `AppNameWatch Watch App/`
### CRITICAL: Embedding the Watch App
⚠️ **THIS IS THE #1 CAUSE OF "WATCH APP NOT INSTALLED" ERRORS** ⚠️
The watch app MUST be embedded in the iOS app for deployment to real devices:
1. Select the **iOS target** in Xcode
2. Go to **Build Phases** tab
3. Verify there's an **"Embed Watch Content"** phase
4. **CRITICAL**: Ensure **"Code Sign On Copy"** is CHECKED ✓
If "Embed Watch Content" doesn't exist:
1. Click **"+"** → **"New Copy Files Phase"**
2. Rename to **"Embed Watch Content"**
3. Set **Destination** to **"Products Directory"**
4. Set **Subpath** to `$(CONTENTS_FOLDER_PATH)/Watch`
5. Add the watch app (e.g., `AppNameWatch Watch App.app`)
6. **CHECK "Code Sign On Copy"** ← This is critical!
Without proper embedding, the iOS app installs but the watch app does NOT install on the paired Apple Watch.
### Bundle Identifiers
Watch app bundle IDs MUST be prefixed with the iOS app's bundle ID:
```
iOS app: com.company.AppName
Watch app: com.company.AppName.watchkitapp ← MUST start with iOS bundle ID
```
Also verify `WKCompanionAppBundleIdentifier` in the watch target's build settings matches the iOS app's bundle ID exactly.
### Data Sync: WatchConnectivity (NOT App Groups)
**DO NOT use App Groups for iPhone ↔ Watch data sharing.**
App Groups:
- ❌ Do NOT work between iPhone and Apple Watch
- ❌ Different container paths on each device
- ❌ Will waste hours debugging why data isn't syncing
- ✅ Only work between an app and its extensions on the SAME device
**Use WatchConnectivity framework instead:**
```swift
// iOS side - WatchConnectivityService.swift
import WatchConnectivity
@MainActor
final class WatchConnectivityService: NSObject, WCSessionDelegate {
static let shared = WatchConnectivityService()
private override init() {
super.init()
if WCSession.isSupported() {
WCSession.default.delegate = self
WCSession.default.activate()
}
}
func syncData(_ data: [String: Any]) {
guard WCSession.default.activationState == .activated,
WCSession.default.isPaired,
WCSession.default.isWatchAppInstalled else { return }
try? WCSession.default.updateApplicationContext(data)
}
}
```
### WatchConnectivity Methods
| Method | Use Case |
|--------|----------|
| `updateApplicationContext` | Latest state that persists (use this for most syncs) |
| `sendMessage` | Immediate delivery when counterpart is reachable |
| `transferUserInfo` | Queued delivery, guaranteed but not immediate |
### watchOS Framework Limitations
These iOS frameworks are NOT available on watchOS:
-`CoreImage` - Generate QR codes on iOS, send image data to watch
-`UIKit` (mostly) - Use SwiftUI
-`AVFoundation` (limited)
### Simulator Limitations
WatchConnectivity on simulators is **unreliable**:
- `isWatchAppInstalled` often returns `false` even when running
- `isReachable` may be `false` even with both apps running
- `updateApplicationContext` may fail with "counterpart not installed"
**Workarounds for simulator testing:**
1. Add `#if targetEnvironment(simulator)` blocks with sample data
2. Test real sync functionality on physical devices only
### Debugging Watch Sync Issues
If `isWatchAppInstalled` returns `false`:
1. ✅ Check "Embed Watch Content" build phase exists
2. ✅ Check "Code Sign On Copy" is enabled
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"
### NSObject Requirement
`WCSessionDelegate` is an Objective-C protocol, so conforming classes must inherit from `NSObject`:
```swift
final class WatchConnectivityService: NSObject, WCSessionDelegate {
// NSObject is required for WCSessionDelegate conformance
}
```
## SwiftData Instructions
If SwiftData is configured to use CloudKit:
- Never use `@Attribute(.unique)`.
- Model properties must have default values or be optional.
- All relationships must be marked optional.
## Model Design: Single Source of Truth
**Computed properties should be the single source of truth for derived data.**
### Name Fields Pattern
When a model has multiple name components (prefix, firstName, middleName, lastName, suffix, etc.), use a computed property for the display name:
```swift
// ✅ GOOD - Computed from individual fields
var fullName: String {
var parts: [String] = []
if !prefix.isEmpty { parts.append(prefix) }
if !firstName.isEmpty { parts.append(firstName) }
if !lastName.isEmpty { parts.append(lastName) }
// ... etc
return parts.joined(separator: " ")
}
// ❌ BAD - Stored displayName that can get out of sync
var displayName: String // Never add this
```
### Benefits
- **Always up to date**: Changes to individual fields are immediately reflected
- **No sync bugs**: No risk of stored value diverging from component fields
- **Simpler code**: No need to update displayName when editing name fields
### Related Properties
If you need different formats for different purposes:
- `fullName` — For display (may include formatting like quotes, parentheses)
- `vCardName` — For export (plain format, no special formatting)
## Localization Instructions
- Use **String Catalogs** (`.xcstrings` files) for localization.
- SwiftUI `Text("literal")` views automatically look up strings in the catalog.
- For strings outside of `Text` views, use `String(localized:)` or a helper extension.
- Store all user-facing strings in the String Catalog.
- Support at minimum: English (en), Spanish-Mexico (es-MX), French-Canada (fr-CA).
- Never use `NSLocalizedString`; prefer `String(localized:)`.
## Design Constants
**Never use raw numeric literals or hardcoded colors directly in views.**
### Values That MUST Be Constants
- **Spacing & Padding**: `Design.Spacing.medium` not `.padding(12)`
- **Corner Radii**: `Design.CornerRadius.large` not `cornerRadius: 16`
- **Font Sizes**: `Design.BaseFontSize.body` not `size: 14`
- **Opacity Values**: `Design.Opacity.strong` not `.opacity(0.7)`
- **Colors**: `Color.Primary.accent` not `Color(red:green:blue:)`
- **Line Widths**: `Design.LineWidth.medium` not `lineWidth: 2`
- **Shadow Values**: `Design.Shadow.radiusLarge` not `radius: 10`
- **Animation Durations**: `Design.Animation.quick` not `duration: 0.3`
- **Component Sizes**: `Design.Size.avatar` not `frame(width: 56)`
### Organization
- Create a `DesignConstants.swift` file using enums for namespacing.
- Extend `Color` with semantic color definitions.
- View-specific constants go at the top of the view struct with a comment.
- Name constants semantically: `accent` not `pointSix`, `large` not `sixteen`.
## Dynamic Type Instructions
- Always support Dynamic Type for accessibility.
- Use `@ScaledMetric` to scale custom dimensions.
- Choose appropriate `relativeTo` text styles based on semantic purpose.
- For constrained UI elements, you may use fixed sizes but document the reason.
- Prefer system text styles: `.font(.body)`, `.font(.title)`, `.font(.caption)`.
## VoiceOver Accessibility Instructions
- All interactive elements must have meaningful `.accessibilityLabel()`.
- Use `.accessibilityValue()` for dynamic state.
- Use `.accessibilityHint()` to describe what happens on interaction.
- Use `.accessibilityAddTraits()` for element type.
- Hide decorative elements with `.accessibilityHidden(true)`.
- Group related elements to reduce navigation complexity.
- Post accessibility announcements for important events.
## Project Structure
- Use a consistent project structure organized by feature.
- Follow strict naming conventions for types, properties, and methods.
- **One public type per file**—break types into separate files.
- Write unit tests for core application logic.
- Only write UI tests if unit tests are not possible.
- Add code comments and documentation as needed.
- Never include secrets or API keys in the repository.
## Documentation Instructions
### MANDATORY: Keep Documentation Files in Sync
⚠️ **These three files MUST be updated together when making architectural changes:**
| File | Purpose |
|------|---------|
| `Agents.md` | Agent instructions and coding standards |
| `README.md` | Product scope, features, and project structure |
| `ai_implementation.md` | AI implementation context and architecture notes |
**When to update all three:**
- Adding new features or services
- Changing architecture patterns (e.g., WatchConnectivity vs App Groups)
- Modifying model structure (e.g., adding/removing properties)
- Learning important lessons (e.g., watch embedding requirements)
- Changing project structure or file organization
**Update checklist:**
1.`Agents.md` — Add instructions/guidelines for the change
2.`README.md` — Update features, architecture, or structure sections
3.`ai_implementation.md` — Update models, services, files, or lessons learned
### General Documentation Rules
- **Keep `README.md` up to date** when adding new functionality.
- Document new features, settings, or mechanics in the README.
- Update the README when modifying existing behavior.
- Include configuration options and special interactions.
- README updates should be part of the same commit as the feature.
- Maintain a `ROADMAP.md` for tracking feature status.
## PR Instructions
- If installed, ensure SwiftLint returns no warnings or errors.
- Verify that documentation reflects any new functionality.
- Check for duplicate code before submitting.
- Ensure all new files follow the one-type-per-file rule.