7.6 KiB
7.6 KiB
SwiftInvoice
Cross-platform offline-first invoice app for freelancers and tradespeople. Flutter/Dart, SQLite via Drift ORM, Riverpod state management, Material 3.
Spec & Plans
- Spec / schema / feature definitions:
docs/SwiftInvoice_Implementation_Plan.md— read before writing any database or feature code. Single source of truth for the product. - Task-by-task implementation plan:
docs/superpowers/plans/2026-03-22-swiftinvoice-mvp.md— 26 tasks across 4 phases, TDD throughout. Usesuperpowers:subagent-driven-developmentorsuperpowers:executing-plansto work through it.
Commands
flutter pub get # Install dependencies
flutter run # Run on connected device/emulator
flutter test # Run all tests
flutter test --coverage # Run tests with coverage report
flutter analyze # Static analysis
dart run build_runner build # Generate Drift code (run after any table change)
dart run build_runner watch # Watch mode for Drift codegen during active DB work
dart format . # Format all Dart files
flutterfire configure # Generate Firebase config files (first-time setup)
flutter build apk --release # Production Android APK
flutter build ipa --release # Production iOS IPA (requires Xcode)
Project Structure
lib/
main.dart # Entry point — ProviderScope, Firebase init, notification init
app.dart # MaterialApp.router, GoRouter provider, theme
core/
database/
database.dart # AppDatabase (Drift) — schema version 1
tables/ # One file per table
daos/ # One DAO per entity
models/enums.dart # DocumentType, DocumentStatus, PaymentMethod, DiscountType
providers/ # Shared/core Riverpod providers
services/ # Business logic: PdfService, NotificationService, SubscriptionService, DocumentCalculator
utils/ # CurrencyFormatter, DateFormatter, UuidGenerator, Validators
theme/ # AppTheme, AppColors
features/ # Feature-first: each feature owns its screens, widgets, notifiers
invoices/
estimates/
clients/
payments/
pdf/
paywall/
onboarding/
settings/
shared/widgets/ # AppScaffold (bottom nav), shared widgets
Coding Rules
- Feature-first folder structure under
lib/features/. Each feature owns its screens, widgets, and notifiers. - Drift table classes in
lib/core/database/tables/, one file per table. DAOs inlib/core/database/daos/, one per entity. - No business logic in widgets. Put it in services or Riverpod providers/notifiers.
- Use
ConsumerWidget/ConsumerStatefulWidgetwith Riverpod. No rawsetStatefor shared state. - All monetary values are INTEGER cents. Tax/discount rates are basis points (825 = 8.25%). Never use doubles for money.
- All primary keys are TEXT UUIDs generated by
generateUuid()inlib/core/utils/uuid_generator.dart. - All tables have
created_atandupdated_atas ISO 8601 TEXT strings. - Prefer
constconstructors. Use named routes via GoRouter.
Riverpod Conventions
- Feature-level providers go in the feature's screen file or its
*_notifier.dart. - Core/shared providers (DAOs, database) go in
lib/core/providers/. - Use
StateNotifierProviderfor mutable feature state (e.g.InvoiceCreatorNotifier). - Use
FutureProvider.familyfor async DAO lookups by ID (e.g.invoiceDetailProvider(docId)). - Use
.autoDisposeon creator screens (invoice, estimate) so form state resets on navigation. - The
paymentNotifierProviderlives inpayment_bottom_sheet.dart— intentional, it is always used in that context.
Key Design Decisions
- Unified documents table: Invoices and estimates share
documentsvia adocument_typediscriminator ('invoice'|'estimate'). Estimate-to-invoice conversion is a deep copy with status update. - Free tier limits enforced at app layer: 3 invoices/month, 2 active clients. Checked in notifiers before any DB write. Subscription tier is cached in
app_settingsfor offline reads. - PDF generation is triggered from
InvoiceDetailScreen._sharePdf(). Logic lives inPdfService. Watermark shown for free tier, hidden for pro/lifetime. TotalRowis a public widget defined ininvoice_creator_screen.dartand reused byestimate_creator_screen.dart. Keep it public (not_TotalRow).- RevenueCat tier caching: tier is stored in
app_settings(subscription_tierkey) and synced on app launch. All tier checks read from local cache for instant offline behavior. - Overdue detection runs on app foreground via
DocumentDao.markOverdueInvoices(). Notifications fire at 9:00 AM the day after the due date viaflutter_local_notifications.
Git Workflow
- Work on a feature branch. Create a branch (
feat/phase-1-foundation,feat/phase-2-invoicing, etc.) per phase. Keepmainclean. - Commit in small increments. One commit per task step: a single table, a single DAO, a single screen. Follow the commit messages in the plan exactly.
- Use conventional commits:
feat:,fix:,refactor:,test:,docs:,chore: - Merge to main only when a phase is complete, all tests pass, and
flutter analyzeis clean. - Do not ask before committing, branching, or merging. Just do it.
Autonomous Execution
- Do not ask clarifying questions. The implementation plan is the spec. If something is ambiguous, make a reasonable decision, leave a comment, and keep going.
- Do not ask before running commands. Run pub get, build_runner, tests, and analyze freely.
- Do not stop to present options or ask for preferences. The tech stack and patterns are decided.
- Do not ask "should I continue?" Always continue to the next task in the plan.
- If a test fails, fix it. Only stop if you cannot resolve it after 3 attempts.
- If you need a dependency, add it to pubspec.yaml, run pub get, and continue.
Gotchas
- Drift requires code generation — run
dart run build_runner buildafter any table change or it won't compile. The generated file islib/core/database/database.g.dart. - The
pdfpackage renders to its own widget tree, not Flutter widgets. They share syntax but are different libraries (package:pdf/widgets.dartvspackage:flutter/widgets.dart). flutter_local_notificationsneeds platform-specific init inMainActivity.kt(Android) andAppDelegate.swift(iOS) in addition to the Dart-sideNotificationService.initialize().- RevenueCat (
purchases_flutter) needs platform-specific setup in bothandroid/andios/— follow their Flutter quickstart. The API key is passed via--dart-define=REVENUECAT_API_KEY=...at build time, never hardcoded. - Firebase config files (
google-services.json,GoogleService-Info.plist) are git-ignored. Generate them withflutterfire configure. The generatedlib/firebase_options.dartIS committed. - Free tier limits (3 invoices/month, 2 clients) are enforced at the notifier layer, not the database. The DB has no constraints for these limits.
DocumentCalculatoruses integer arithmetic throughout. Discount basis points and tax basis points both divide by 10000 (so 825 bp = 8.25%). ThediscountValueBasisPointsparameter holds cents (not basis points) whendiscountType == 'fixed'— the naming is a known inconsistency, don't "fix" it.