Files
SwiftInvoice/CLAUDE.md
T
2026-03-23 00:27:04 +00:00

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. Use superpowers:subagent-driven-development or superpowers:executing-plans to 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 in lib/core/database/daos/, one per entity.
  • No business logic in widgets. Put it in services or Riverpod providers/notifiers.
  • Use ConsumerWidget / ConsumerStatefulWidget with Riverpod. No raw setState for 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() in lib/core/utils/uuid_generator.dart.
  • All tables have created_at and updated_at as ISO 8601 TEXT strings.
  • Prefer const constructors. 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 StateNotifierProvider for mutable feature state (e.g. InvoiceCreatorNotifier).
  • Use FutureProvider.family for async DAO lookups by ID (e.g. invoiceDetailProvider(docId)).
  • Use .autoDispose on creator screens (invoice, estimate) so form state resets on navigation.
  • The paymentNotifierProvider lives in payment_bottom_sheet.dart — intentional, it is always used in that context.

Key Design Decisions

  • Unified documents table: Invoices and estimates share documents via a document_type discriminator ('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_settings for offline reads.
  • PDF generation is triggered from InvoiceDetailScreen._sharePdf(). Logic lives in PdfService. Watermark shown for free tier, hidden for pro/lifetime.
  • TotalRow is a public widget defined in invoice_creator_screen.dart and reused by estimate_creator_screen.dart. Keep it public (not _TotalRow).
  • RevenueCat tier caching: tier is stored in app_settings (subscription_tier key) 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 via flutter_local_notifications.

Git Workflow

  • Work on a feature branch. Create a branch (feat/phase-1-foundation, feat/phase-2-invoicing, etc.) per phase. Keep main clean.
  • 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 analyze is 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 build after any table change or it won't compile. The generated file is lib/core/database/database.g.dart.
  • The pdf package renders to its own widget tree, not Flutter widgets. They share syntax but are different libraries (package:pdf/widgets.dart vs package:flutter/widgets.dart).
  • flutter_local_notifications needs platform-specific init in MainActivity.kt (Android) and AppDelegate.swift (iOS) in addition to the Dart-side NotificationService.initialize().
  • RevenueCat (purchases_flutter) needs platform-specific setup in both android/ and ios/ — 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 with flutterfire configure. The generated lib/firebase_options.dart IS 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.
  • DocumentCalculator uses integer arithmetic throughout. Discount basis points and tax basis points both divide by 10000 (so 825 bp = 8.25%). The discountValueBasisPoints parameter holds cents (not basis points) when discountType == 'fixed' — the naming is a known inconsistency, don't "fix" it.