Grain is a native iOS receipt scanner and granular expense tracker (SwiftUI + SwiftData, iOS 17+). It uses Apple frameworks only — no third-party dependencies (see ADR-0003).
- Xcode 16+ (developed against Xcode 26.5 / iOS 26 SDK). Deployment target: iOS 17.0.
- Open
grain.xcodeproj(orgrain.xcworkspace) and run thegrainscheme on an iPhone 17 simulator or a device. - No SPM / CocoaPods / CLI build tooling — it's a pure Xcode project using file-system synchronized groups, so new files under
grain/are picked up automatically (no project-file edits needed).
xcodebuild build -scheme grain -project grain.xcodeproj \
-destination 'platform=iOS Simulator,name=iPhone 17' -configuration Debug CODE_SIGNING_ALLOWED=NO
xcodebuild test -scheme grain -project grain.xcodeproj \
-destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:grainTestsA clean run ends with ** BUILD SUCCEEDED ** / ** TEST SUCCEEDED **. Piping to tail can mask the real exit code — read the final line, or write to a log and grep.
grain/Models/— SwiftData@Modeltypes: Receipt, ReceiptItem, Product, PricePoint, Brand, BankTransaction.grain/Services/— OCR + extraction (ReceiptScannerService, theReceiptExtractortiers,ExtractorCoordinator), analytics (AnalyticsService), demo data (DemoDataSeeder).grain/Views/— SwiftUI screens. Root isMainTabView(receipts / scan / analytics / index / settings).grain/GrainTheme.swift— design tokens.docs/— architecture audit, ADRs, specs.
- Design system. Use
GrainThemetokens for every color, font, and spacing value. Typography is monospace viaGrainTheme.mono(...). Never hardcode colors. - Architecture decisions. Significant technical decisions get an ADR in
docs/adr/(see the ADR README for the format). Reference the relevant ADR in your PR. - No external dependencies. Apple frameworks only, unless an ADR explicitly approves an exception (ADR-0003).
- Models. Declare
@Relationship(deleteRule:)on to-many relationships; useDecimal(neverDouble) for money; includeid/createdAt/updatedAt. Register new models in the schema ingrainApp.swift, inDemoDataSeeder.makePreviewContainer(), and in any#Previewcontainer. - Errors. Prefer user-facing alerts over swallowing errors with
print()(known tech debt being paid down).
- Branch from
mainwith a descriptive name (feature/...,fix/...). - Update
CHANGELOG.mdunder[Unreleased]for user-facing changes. - Keep PRs focused; CI builds + tests on every PR and validates docs.
- Unit tests live in
grainTests/(model, service, and parser coverage); UI smoke tests ingrainUITests/. - Add or extend tests for new services and parsing logic.