New Release-DeveloperID configuration gates Sparkle behind a SPARKLE compile flag so the App Store Release build stays Sparkle-free. Adds SPUStandardUpdaterController wrapper, Check for Updates menu command, Advanced Settings section with beta channel toggle, and a Ruby script plus GitHub Actions job that signs each release and publishes the appcast to gh-pages for consumption by Sparkle and Homebrew cask.
3.8 KiB
Yattee Development Guide for AI Agents
Deployment Targets
iOS: 18.0+ | macOS: 15.0+ | tvOS: 18.0+
This project targets the latest OS versions only - use newest APIs freely without availability checks.
Build & Test Commands
Build: xcodebuild -scheme Yattee -configuration Debug
Test (all): xcodebuild test -scheme Yattee -destination 'platform=macOS'
Test (single): xcodebuild test -scheme Yattee -destination 'platform=macOS' -only-testing:YatteeTests/TestSuiteName/testMethodName
Lint: periphery scan (config: .periphery.yml)
Build Configurations
Three configurations exist, mapped to distribution channels:
| Configuration | Sparkle (#if SPARKLE) |
Used for |
|---|---|---|
Debug |
off | local development, tests |
Release |
off | App Store / TestFlight (fastlane mac beta) — must stay Sparkle-free, App Review rejects auto-update frameworks |
Release-DeveloperID |
on | Developer ID notarized build (fastlane mac build_and_notarize), distributed via GitHub Releases + Homebrew cask, receives Sparkle updates |
All Sparkle-dependent code must be wrapped in #if SPARKLE ... #endif so the Release variant links zero Sparkle symbols. When adding new Sparkle features, test both configs build clean on macOS.
Code Style
Language: Swift 5.0+ with strict concurrency (Swift 6 mode enabled)
UI: SwiftUI with @Observable macro for view models (not ObservableObject)
Concurrency: Use actor for services, @MainActor for UI-related code, async/await everywhere
Testing: Swift Testing framework (@Test, @Suite, #expect) - NOT XCTest
Imports & Organization
Import order: Foundation first, then SwiftUI, then @testable imports
File headers: Include // FileName.swift, // Yattee, and brief comment describing purpose
MARK comments: Use // MARK: - Section Name to organize code sections
Sendable: All models, errors, and actors must conform to Sendable
Types & Naming
Models: Immutable structs with Codable, Hashable, Sendable conformance
Services: Use actor for thread-safe services, final class for @Observable view models
Enums: Use associated values for typed errors (see APIError.swift)
Optionals: Prefer guard-let unwrapping; use if let for simple cases
Naming: camelCase for variables/functions, PascalCase for types, clear descriptive names
Error Handling
Errors: Define typed enum errors conforming to Error, LocalizedError, Equatable, Sendable
Async throws: All async network/IO operations should throw typed errors
Logging: Use LoggingService.shared for all logging (see HTTPClient.swift for patterns)
User feedback: Provide localized error descriptions via errorDescription
Testing & Debugging
Add logging/visual clues (borders, backgrounds) when debugging issues - then ask user for results
If first fix doesn't work: Add debug code before second attempt to understand the issue better
UI Testing (Ruby/RSpec with AXe CLI)
Run UI tests: ./bin/ui-test --skip-build --keep-simulator
Run single spec: SKIP_BUILD=1 KEEP_SIMULATOR=1 bundle exec rspec spec/ui/smoke/search_spec.rb
Accessibility labels vs identifiers: On iOS 26+, .accessibilityIdentifier() doesn't work reliably on Group, ScrollView, and some container views (AXUniqueId comes back empty). Use .accessibilityLabel() instead, which maps to AXLabel and can be detected via AXe's text_visible?() method.
iOS 26 TabView search: The search field is integrated into the bottom tab bar with Tab(role: .search). Typing \n doesn't submit - use hardware key press via press_return (AXe key 40).
ScrollView children: Video rows inside LazyVStack/ScrollView aren't exposed in the accessibility tree. Use coordinate-based tapping instead.