All checks were successful
CI / Scraper / Lint (push) Successful in 10s
CI / Scraper / Test (push) Successful in 14s
Release / Scraper / Test (push) Successful in 18s
CI / Scraper / Lint (pull_request) Successful in 18s
Release / UI / Build (push) Successful in 23s
CI / Scraper / Test (pull_request) Successful in 15s
CI / UI / Build (pull_request) Successful in 32s
Release / Scraper / Docker (push) Successful in 55s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
CI / Scraper / Docker Push (push) Successful in 1m5s
Release / UI / Docker (push) Successful in 1m12s
iOS CI / Build (push) Successful in 4m18s
iOS CI / Build (pull_request) Successful in 4m25s
iOS CI / Test (push) Successful in 8m11s
iOS CI / Test (pull_request) Successful in 8m21s
5.0 KiB
5.0 KiB
LibNovel iOS App
SwiftUI app targeting iOS 17+. Consumes the Go scraper HTTP API for books, chapters, and audio. Uses MinIO presigned URLs for media playback and downloads.
Project Structure
ios/LibNovel/LibNovel/
├── App/ # LibNovelApp.swift, ContentView.swift, RootTabView.swift
├── Models/ # Models.swift (all domain types)
├── Networking/ # APIClient.swift (URLSession-based HTTP client)
├── Services/ # AudioPlayerService, AudioDownloadService, AuthStore,
│ # BookVoicePreferences, NetworkMonitor
├── ViewModels/ # One per view/feature (HomeViewModel, BrowseViewModel, etc.)
├── Views/
│ ├── Auth/ # AuthView
│ ├── BookDetail/ # BookDetailView, CommentsView
│ ├── Browse/ # BrowseView (infinite scroll shelves)
│ ├── ChapterReader/ # ChapterReaderView, DownloadAudioButton
│ ├── Common/ # CommonViews (shared reusable components)
│ ├── Components/ # OfflineBanner
│ ├── Downloads/ # DownloadsView, DownloadQueueButton
│ ├── Home/ # HomeView
│ ├── Library/ # LibraryView (2-col grid, filters)
│ ├── Player/ # PlayerViews (floating FAB, compact, full-screen)
│ ├── Profile/ # ProfileView, VoiceSelectionView, UserProfileView, etc.
│ └── Search/ # SearchView
└── Extensions/ # NavDestination.swift, String+App.swift, Color+App.swift
iOS / Swift Conventions
- Deployment target: iOS 17.0 — use iOS 17+ APIs freely.
- Observable pattern: The codebase currently uses
@StateObject/ObservableObject/@Published. When adding new types, prefer the@Observablemacro (iOS 17+) overObservableObject. Do not refactor existing types unless explicitly asked. - Navigation: Use
NavigationStack(notNavigationView). Use.navigationDestination(for:)for type-safe routing. - Concurrency: Use
async/awaitand structured concurrency. Avoid callback-based APIs andDispatchQueue.main.async— prefer@MainActororawait MainActor.run. - State management: Prefer
@State+@Bindingfor local UI state. Use environment objects for app-wide services (authStore, audioPlayer, downloadService, networkMonitor). - SwiftData: Not currently used. Do not introduce SwiftData without discussion.
- SF Symbols: Use
Image(systemName:)for icons. No emoji in UI unless already present.
Key Patterns
- Download keys: Use
::as separator (e.g.,"slug::chapter-1::voice"), never-. Slugs contain hyphens. - Voice fallback chain: book override → global default →
"af_bella". SeeBookVoicePreferences.voiceWithFallback(). - Offline handling: Wrap view bodies in
VStackwithOfflineBannerat top. UseNetworkMonitor(environment object) to gate network calls. Suppress network errors silently when offline viaErrorAlertModifier. - Audio playback priority: local file → MinIO presigned URL → trigger TTS generation.
- Progress display: Show decimal % when < 10% (e.g., "3.4%"), rounded when >= 10% (e.g., "47%").
- Cover images: Always proxy via
/api/cover/{domain}/{slug}— never link directly to source.
Networking
APIClient.swift wraps all Go scraper API calls. When adding new endpoints:
- Add a method to
APIClient. - Keep error handling consistent — throw typed errors, let ViewModels catch and set
errorMessage. - All requests are relative to
SCRAPER_API_URL(configured at build time via xcconfig or environment).
Using Documentation Tools
When writing or reviewing SwiftUI/Swift code:
- Use
context7to look up current Apple SwiftUI/Swift documentation before implementing anything non-trivial. Apple's APIs evolve fast — do not rely on training data alone. - Use
gh_grepto find real-world Swift patterns when unsure how something is typically implemented.
Example prompts:
- "How does
.searchablework in iOS 17? use context7" - "Show me examples of
@Observablewith async tasks. use context7" - "How do other apps implement background URLSession downloads in Swift? use gh_grep"
UI/UX Skill
For any iOS view work, always load the ios-ux skill at the start of the task:
skill({ name: "ios-ux" })
This skill defines the full design system, animation rules, haptic feedback policy, accessibility checklist, performance guidelines, and offline handling requirements. It also governs how to handle screenshot-based reviews (analyze → suggest → confirm before applying).
What to Avoid
NavigationView— deprecated, useNavigationStackObservableObject/@Publishedfor new types — prefer@ObservableDispatchQueue.main.async— prefer@MainActor- Force unwrapping optionals
- Hardcoded color literals — use
Color+App.swiftextensions or semantic colors - Adding new dependencies (SPM packages) without discussion