Architecture and engineering rules

SwiftUI + SwiftData, the CreditMateKit package, integer money, ppm rates, testing.

CreditMateUpdated 21 Oct 2026
kiến trúcswift

CreditMate is built with SwiftUI and SwiftData on Swift 6 with strict concurrency checking, targeting iOS and iPadOS 18 or later. The central idea of the architecture is to separate business logic from the interface, so that calculations involving money can be tested independently and repeatedly.

Code structure

The app contains only the interface and the wiring. All logic lives in a Swift package called CreditMateKit, with these targets:

Target Role
Money Exact money, ratios, the bank calendar, display styles
Domain Ledger, card catalog, installments, loans, offers, MCC, analytics, CIC reports
Persistence SwiftData models, versioned migrations, encrypted backup
DocumentIntake PDF, image and CSV import; statement and CIC readers that produce drafts
CardImage On-device masking of card photos
Vault The payment-details vault
BankKnowledge Card knowledge and offers library
Assistant Conversation, tools, memory, budget
AssistantProviders Apple on-device, Gemini, Claude, ChatGPT, OpenAI-compatible local AI

The Xcode project is generated by XcodeGen from project.yml. Besides the main app there is a Share Extension that receives shared links and images into "Shared items".

Rules for money and data

  • Money is an integer. An amount is a quantity in the smallest unit under ISO 4217 (dong for VND, cent for USD, yen for JPY) plus a currency code. No floating point numbers for money, ratios or exchange rates.
  • Ratios are parts per million (ppm). 6.8% is stored as 68,000. Multiplication and division use integers with explicit rounding rules and overflow checks.
  • Unknown is not zero. An unknown value is nil and is never replaced with 0 when adding or displaying. Two currencies without a sourced exchange rate are never added together.
  • Dates are LocalDate. A calendar date is a year, month and day; the default bank time zone is Asia/Ho_Chi_Minh. Time is injected through a Clock, so tests do not depend on the device clock. Statement date, due date, purchase date and posting date are four separate concepts.
  • Domain is pure Swift. Money and Domain import no SwiftUI, SwiftData or networking.
  • No cascade deletes of history. A card or account with transactions is archived, not deleted.
  • A limit is stored once at the shared limit group.

CloudKit compatibility

SwiftData models are written to be ready for iCloud sync: no uniqueness constraints, every property is either optional or has a default value, relationships are optional, and financial data has no cascade delete. Duplicate prevention happens in the service layer through idempotency keys and external references, with tests. CloudKit is not enabled yet.

Drafts and posting

Every input source, including the AI assistant, only creates drafts. Posting happens only through an Application Service after the user confirms, with a guard against double posting. This is a project rule, not an arbitrary habit.

Testing

  • Domain tests for the package: Money, Domain, Persistence, DocumentIntake, Assistant, Vault and other targets, run with ./scripts/test-kit.sh. Money calculations are covered by tests written first, based on a set of scenarios with fictional figures.
  • UI tests run on iPhone and iPad, on iOS 18 and iOS 27. The default test run uses Vietnamese, with an additional smoke test for English.
  • There are tests for the balance-recording rules, no double posting on re-import, totals that never turn nil into 0, and currencies other than VND.

Developing with Claude Code

The project is developed with the help of Claude Code. A rule set in the repository's CLAUDE.md file constrains how work is done: rules about money, drafts and security, and a requirement that each important function carries a comment explaining its business meaning. Design decisions are recorded as separate documents. Results must be verified by running real tests, not just by description.