Version: 1.0 Date: 2026-01-22 Status: Draft Target Platform: iOS 17.0+ (Current Generation Devices)
SwiftWing is a native iOS application that transforms book spine scanning into a frictionless experience. Using advanced camera technology and AI-powered analysis via the Talaria backend, SwiftWing enables users to build and manage their personal library with a simple tap.
Core Value Proposition:
- Zero-lag camera - See your bookshelf instantly
- One-tap scanning - Capture multiple spines in seconds
- Real-time enrichment - AI identifies and enriches books as you scan
- Offline-first - Works without connectivity, syncs when online
- Swiss precision - High-contrast UI optimized for OLED devices
- Instant Capture - Cold start to live camera feed in < 0.5 seconds
- Rapid Scanning - Process 10+ book spines per minute
- High Accuracy - 95%+ successful identification rate via Talaria AI
- Native Performance - 60 FPS UI, no jank during scanning
- Data Ownership - Full local storage with CSV export capability
| Metric | Target | Measurement |
|---|---|---|
| Camera Launch Time | < 0.5s | Instrumentation |
| Scan Success Rate | > 95% | Analytics |
| App Rating | > 4.5 stars | App Store |
| Daily Active Users | 1K+ | Analytics |
| Crash-Free Sessions | > 99.5% | Crash reporting |
Book Collectors & Enthusiasts
- Own 100+ physical books
- Want digital catalog without manual entry
- Value speed and accuracy over customization
- Appreciate minimal, functional design
Demographics:
- Age: 25-55
- Tech-savvy iOS users
- Own current-gen iPhones (iPhone 14+)
- Prefer native apps over web tools
- New Collection Inventory - Scan entire bookshelf in one session
- Book Shopping - Check if book is already owned while browsing
- Library Management - Quick lookup and search of owned books
- Data Portability - Export collection for other tools/services
| Component | Technology | Rationale |
|---|---|---|
| UI Framework | SwiftUI | Declarative, native performance, modern |
| State Management | Observation Framework | iOS 17+ replacement for Combine/ObservableObject |
| Data Persistence | SwiftData | Modern Core Data replacement, SwiftUI integration |
| Concurrency | Swift 6 (async/await, actors) | Thread-safe, compiler-enforced |
| Camera | AVFoundation | Low-level control, maximum performance |
| Networking | URLSession | Native, no third-party dependencies |
| Real-time Updates | Server-Sent Events (SSE) | One-way streaming from backend |
| Service | Endpoint | Purpose |
|---|---|---|
| Talaria API | POST /v3/jobs/scans | Image upload & job creation |
| SSE Stream | GET {streamUrl} | Real-time progress & results |
| Cleanup | DELETE /v3/jobs/scans/{jobId}/cleanup | Resource cleanup |
- iOS: 17.0+
- Device: iPhone 14 or newer (A15 Bionic+)
- Storage: 100 MB app + user data
- Network: Optional (offline-capable)
┌─────────────────────────────────────────────────────────┐
│ SwiftUI Layer │
│ • CameraView (AVFoundation preview) │
│ • LibraryGridView (LazyVGrid + AsyncImage) │
│ • BookDetailSheet (Metadata editing) │
└────────────────┬────────────────────────────────────────┘
│
┌────────────────▼────────────────────────────────────────┐
│ ViewModel Layer (@Observable) │
│ • CameraViewModel │
│ • LibraryViewModel │
│ • ScanJobViewModel │
└────────────────┬────────────────────────────────────────┘
│
┌────────────────▼────────────────────────────────────────┐
│ Actor-Based Services │
│ • CameraActor (AVCaptureSession isolation) │
│ • NetworkActor (HTTP + SSE) │
│ • DataSyncActor (SwiftData writes) │
└────────────────┬────────────────────────────────────────┘
│
┌────────────────▼────────────────────────────────────────┐
│ Data Layer (SwiftData) │
│ • Book @Model │
│ • ScanJob @Model │
│ • ModelContainer (persistent store) │
└─────────────────────────────────────────────────────────┘
Actors for Isolation:
CameraActor- Manages AVCaptureSession, prevents data racesNetworkActor- Handles uploads and SSE streamsDataSyncActor- Coordinates SwiftData writes
AsyncStream for SSE:
actor NetworkActor {
func streamEvents(from url: URL) -> AsyncThrowingStream<SSEEvent, Error> {
// Wrap SSE connection in async stream
}
}Main Actor for UI:
@MainActor
@Observable
class CameraViewModel {
var isScanning: Bool = false
var activeJobs: [ScanJob] = []
}Scope:
- Project setup with SwiftData + Observation
- Swiss Utility design system
- Secure device identity (Keychain)
- Offline-first network layer
Technical Requirements:
- Bundle ID:
com.ooheynerds.swiftwing - Min Deployment: iOS 17.0
- SwiftData container configuration
- Network path monitoring with
NWPathMonitor
Scope:
- Zero-lag camera initialization
- Non-blocking shutter action
- Background image processing
- Manual focus & zoom controls
Key Features:
- Performance Target: < 0.5s cold start
- Frame Rate: 30 FPS (balance quality vs battery)
- Pixel Format: YUV 420 for efficiency
- Preview: Full-screen with status bar hidden
- UI: 80px white ring at bottom center
- Feedback: Haptic impact + white flash overlay
- Behavior: Non-blocking (rapid-fire capable)
- Isolation:
Task.detachedwith.userInitiatedpriority - Resize: Max 1920px long-edge
- Compression: JPEG quality 0.85
- Storage: FileManager temp directory
- Layout: Horizontal ScrollView above shutter
- Items: 40x60px thumbnails
- State Colors:
- Yellow border: Uploading
- Blue border: Analyzing
- Green border: Done
Scope:
- Multipart image upload
- Server-Sent Events (SSE) listener
- Real-time progress visualization
- Result handling & data persistence
API Flow:
1. Capture Image
↓
2. POST /v3/jobs/scans
← 202 { jobId, streamUrl }
↓
3. Open SSE Stream (streamUrl)
← event: progress → "Looking..."
← event: progress → "Reading..."
← event: result → { isbn, title, author, ... }
← event: complete
↓
4. Save to SwiftData
↓
5. DELETE /v3/jobs/scans/{jobId}/cleanup
Error Handling:
- 429 Too Many Requests: Disable shutter, show countdown
- Network Loss: Queue locally, retry when online
- Timeout: 5-minute max per SSE stream
- Retry Logic: Exponential backoff (1s, 2s, 4s max 3 attempts)
Scope:
- SwiftData schema definition
- Library grid view with lazy loading
- Real-time list updates via
@Query - Full-text search
- Review-needed indicator
Data Model:
@Model
final class Book {
@Attribute(.unique) var isbn: String
var title: String
var author: String
var coverUrl: URL?
var format: String?
var addedDate: Date
var spineConfidence: Double?
// Computed
var needsReview: Bool {
guard let confidence = spineConfidence else { return false }
return confidence < 0.8
}
}UI Components:
- Grid: LazyVGrid with adaptive columns (min 100px)
- Search:
.searchable()with predicate filtering - Sort: Default by
addedDatedescending - Empty State: ContentUnavailableView with instructions
Scope:
- Minimal book detail sheet
- Raw JSON toggle
- Context menu delete
- Haptic feedback strategy
- Cache management
Book Detail Sheet:
- Presentation:
.sheet()with.medium/.largedetents - Layout: HStack { Cover | VStack { Metadata } }
- Edit Mode: Inline TextFields for corrections
- Raw View: Monospaced JSON in green syntax highlighting
Haptic Strategy:
| Action | Feedback Type |
|---|---|
| Shutter tap | .impact |
| Scan success | .success |
| Error/retry | .error |
Scope:
- App icon & launch screen
- Permission priming screens
- Empty state designs
- Error overlay system
App Icon:
- Solid black background
- White abstract wing glyph
- Adaptive for iOS
Launch Screen:
- Black background (#000000)
- "SwiftWing" in JetBrains Mono
- Fade out after 500ms or camera ready
Philosophy: Blend high-contrast minimalism with iOS 26's Liquid Glass aesthetic. Precision meets platform convention.
Balance: 60% Swiss Utility / 40% Liquid Glass
| Usage | Typeface | Weight | Size | Rationale |
|---|---|---|---|---|
| Data/IDs | JetBrains Mono | Regular | 16-24pt | Brand identity |
| UI Labels | San Francisco Pro | Regular | 14-16pt | Native readability |
| Captions | San Francisco Pro | Medium | 12pt | iOS convention |
extension Color {
static let swissBackground = Color.black // #000000 (Swiss)
static let swissText = Color.white // #FFFFFF (Swiss)
static let internationalOrange = Color( // #FF3B30 (Swiss accent)
red: 1.0, green: 0.23, blue: 0.19
)
}Usage:
- Background: Pure black (OLED optimization, Swiss base)
- Text: Pure white (maximum contrast, Swiss)
- Accent: International Orange (CTAs, borders, Swiss identity)
- Glass:
.ultraThinMaterialfor overlays (Liquid Glass)
Materials (Liquid Glass):
.background(.ultraThinMaterial) // Translucent overlays
.background(.thinMaterial) // Menus, sheetsCorners & Shapes:
.clipShape(RoundedRectangle(cornerRadius: 12)) // Soft rounds (Glass)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(.white, lineWidth: 2) // White borders (Swiss)
)Shadows (Minimal):
.shadow(color: .white.opacity(0.1), radius: 8) // Subtle glow onlyMotion:
.spring(duration: 0.2) // Fluid animations (Glass)
.linear(duration: 0.1) // Quick fades (Swiss)Haptics:
.sensoryFeedback(.impact) // Shutter
.sensoryFeedback(.success) // Scan complete
.sensoryFeedback(.error) // Error stateShutter Button:
ZStack {
Circle()
.fill(.white.opacity(0.05))
.background(.ultraThinMaterial.opacity(0.3), in: Circle()) // Glass
Circle()
.stroke(.white, lineWidth: 4) // Swiss precision
}
.frame(width: 80, height: 80)
.shadow(color: .white.opacity(0.1), radius: 8) // Minimal glowProcessing Queue Thumbnails:
thumbnailView
.frame(width: 40, height: 60)
.background(.ultraThinMaterial) // Glass effect
.clipShape(RoundedRectangle(cornerRadius: 8)) // Rounded
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(stateColor, lineWidth: 2) // Colored borders (Swiss)
)1. Launch app
↓
2. Permission primer screen
"SwiftWing needs camera to see books"
[Continue] button
↓
3. System camera permission prompt
↓
4. Camera view (full screen, live feed)
- Shutter button at bottom
- Empty processing queue
↓
5. User taps shutter
- Haptic feedback
- White flash
- Thumbnail appears in queue (yellow border)
↓
6. Image uploads → SSE stream opens
- Border turns blue
- "Reading..." text overlay
↓
7. Result arrives
- Border turns green
- Haptic success
- Book added to library
↓
8. User swipes to Library tab
- Book appears in grid
- Cover image loads asynchronously
1. User opens to camera view
↓
2. Rapid-fire shutter taps (10+ scans)
- Each tap adds thumbnail to queue
- UI never blocks
↓
3. Processing happens in parallel
- Multiple SSE streams active
- Queue shows mixed states (yellow/blue/green)
↓
4. Results arrive out-of-order
- Each success triggers haptic
- Books appear in library in real-time
↓
5. All scans complete
- Queue auto-clears after 5s per item
1. Network disconnects mid-session
↓
2. "OFFLINE" indicator appears (top-right)
↓
3. User continues scanning
- Images queue locally
- Thumbnails show in queue (gray border)
↓
4. Network reconnects
- "OFFLINE" indicator disappears
- Queued images auto-upload
- SSE streams open for all queued jobs
| Metric | Target | Critical Path |
|---|---|---|
| Camera cold start | < 0.5s | App launch → live feed |
| Shutter responsiveness | < 50ms | Tap → haptic + flash |
| Image processing | < 500ms | Capture → upload ready |
| UI frame rate | > 55 FPS | During active scanning |
| SSE connection time | < 200ms | Upload response → stream open |
- Crash-Free Rate: > 99.5%
- Data Loss: Zero tolerance (all scans must persist or retry)
- Offline Mode: 100% functional camera, queues sync on reconnect
- Device ID: Stored in iOS Keychain (survives app reinstall)
- API Keys: Never in source code (environment or secure backend)
- Image Data: Deleted from temp directory after upload
- Network: TLS 1.3+ for all Talaria communication
- Camera Usage: Explicit permission with clear primer
- Data Collection: Only device ID + scanned book metadata
- Analytics: Opt-in, anonymized crash/performance data
- No Tracking: No third-party SDKs, no ads
- Dynamic Type: Support system font scaling
- VoiceOver: Label all interactive elements
- Haptics: Provide visual alternatives for deaf users
- Contrast: WCAG AAA (black/white already compliant)
| Service | Purpose | Criticality | Fallback |
|---|---|---|---|
| Talaria Backend | Book recognition & enrichment | Critical | Offline queue |
| Cover Image CDN | Book cover downloads | Medium | Gray placeholder |
None. SwiftWing uses only native Apple frameworks to minimize dependencies and maximize performance.
- Talaria API is stable and available 99.9% uptime
- iOS 17+ adoption is sufficient in target market (iPhone 14+)
- Users have network connectivity most of the time (offline is secondary)
- Book ISBN barcodes are reliably extracted from spine images
- Users scan < 100 books per session on average
- iOS Only - No Android, web, or tablet versions in MVP
- English Language - UI text in English only initially
- Rate Limits - Talaria enforces per-device daily scan limits
- Camera Required - App unusable without camera permission
- Current Gen Only - No backward compatibility for iPhone 13 or older
| Risk | Impact | Probability | Mitigation |
|---|---|---|---|
| Talaria API downtime | High | Low | Local queue, retry logic, offline mode |
| iOS API changes (iOS 18) | Medium | Medium | Pin to iOS 17 SDK, test betas early |
| Poor lighting reduces accuracy | High | Medium | ML preprocessing, flash option, user guidance |
| Rate limit frustration | Medium | High | Clear messaging, countdown timer, daily quota display |
| Battery drain from camera | Medium | Medium | Auto-pause after 30s, optimize frame processing |
- ✅ Camera launches in < 0.5s
- ✅ Non-blocking shutter allows rapid scanning
- ✅ SSE stream receives real-time updates
- ✅ Books persist to SwiftData
- ✅ Library grid displays with covers
- ✅ Offline mode queues scans
- ✅ Swiss Utility design fully implemented
- ✅ Manual focus and zoom
- ✅ Full-text search in library
- ✅ Processing queue UI with state colors
- ✅ Haptic feedback strategy
- ✅ Rate limit handling
- ✅ Permission priming screens
- ✅ Review-needed indicator
- ✅ CSV export
- ✅ Raw JSON toggle
- ✅ Cache management
- ✅ Empty states
- Multi-language support (Spanish, French, etc.)
- iPad version with optimized layout
- Batch editing of metadata
- Collections/shelves organization
- Loan tracking (who borrowed what)
- Reading progress tracking
- Integration with Goodreads, LibraryThing
- Project setup + SwiftData schema
- Swiss Utility design system
- Device ID + network layer
- Milestone: Clean build, basic UI shell
- AVFoundation integration
- Shutter + image processing
- Processing queue UI
- Milestone: Can capture and process images locally
- Talaria API client
- SSE stream implementation
- Real-time result handling
- Milestone: End-to-end scan → enrichment → library
- Library grid + search
- Book detail sheet
- Error handling + offline mode
- Milestone: Feature complete MVP
- Unit + integration tests
- Performance optimization
- App Store assets + submission
- Milestone: Live on App Store
- Talaria API Rate Limits - Exact quotas per device per day?
- Cover Image CDN - CORS policy, rate limits, caching headers?
- Analytics Platform - Firebase? TelemetryDeck? Native only?
- Crash Reporting - Sentry? Bugsnag? None?
- Beta Testing - TestFlight external beta size?
- App Store Pricing - Free? Paid upfront? Freemium with IAP?
- Backend SLA - What uptime guarantee does Talaria provide?
- US-swift.md - Detailed user stories (US-101 to US-130)
- findings.md - Technical research and decisions
- task_plan.md - Development task plan
- flutter-legacy/prd.json - Flutter version reference
| Term | Definition |
|---|---|
| SSE | Server-Sent Events - one-way streaming protocol |
| SwiftData | Apple's modern persistence framework (iOS 17+) |
| Actor | Swift concurrency primitive for thread-safe isolation |
| Talaria | Backend service providing AI book recognition |
| Swiss Utility | Design aesthetic: high contrast, minimal, functional |
| MVP | Minimum Viable Product |
| P0/P1/P2/P3 | Priority levels (0=Critical, 3=Low) |
Product Owner: [TBD] Lead Developer: [TBD] Backend Team: Talaria API team Design: Swiss Utility aesthetic (no external designer)
Document Status: Draft Next Review: After user feedback on scope Approval Required: Product Owner, Engineering Lead