Update CLAUDE.md
This commit is contained in:
@@ -2,52 +2,107 @@
|
|||||||
|
|
||||||
Cross-platform offline-first invoice app for freelancers and tradespeople. Flutter/Dart, SQLite via Drift ORM, Riverpod state management, Material 3.
|
Cross-platform offline-first invoice app for freelancers and tradespeople. Flutter/Dart, SQLite via Drift ORM, Riverpod state management, Material 3.
|
||||||
|
|
||||||
## Spec
|
## Spec & Plans
|
||||||
|
|
||||||
The full implementation plan, database schema, and feature specs live in `docs/SwiftInvoice_Implementation_Plan.md`. **Read it before writing any database or feature code.** It is the single source of truth for this project.
|
- **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
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flutter pub get # Install dependencies
|
flutter pub get # Install dependencies
|
||||||
flutter run # Run on connected device/emulator
|
flutter run # Run on connected device/emulator
|
||||||
flutter test # Run all tests
|
flutter test # Run all tests
|
||||||
flutter analyze # Static analysis
|
flutter test --coverage # Run tests with coverage report
|
||||||
dart run build_runner build # Generate Drift code (run after any table change)
|
flutter analyze # Static analysis
|
||||||
dart format . # Format all Dart files
|
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
|
## Coding Rules
|
||||||
|
|
||||||
- Feature-first folder structure under `lib/features/`. Each feature owns its screens, widgets, and providers.
|
- 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.
|
- 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.
|
- No business logic in widgets. Put it in services or Riverpod providers/notifiers.
|
||||||
- Use `ConsumerWidget` / `ConsumerStatefulWidget` with Riverpod. No raw `setState` for shared state.
|
- Use `ConsumerWidget` / `ConsumerStatefulWidget` with Riverpod. No raw `setState` for shared state.
|
||||||
- All monetary values are INTEGER cents. Tax rates are basis points (825 = 8.25%). Never use doubles for money.
|
- 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. All tables have `created_at` and `updated_at` ISO 8601 timestamps.
|
- 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.
|
- 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
|
## Git Workflow
|
||||||
|
|
||||||
- **Work on a feature branch.** Create a branch (`feat/feature-name`) for each feature or phase. Keep `main` clean.
|
- **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 logical unit: a single table, a single screen, a single service. Not one giant commit per feature.
|
- **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:`
|
- **Use conventional commits:** `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`
|
||||||
- **Merge to main only when a feature is complete**, tests pass, and `flutter analyze` is clean.
|
- **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.
|
- **Do not ask before committing, branching, or merging.** Just do it.
|
||||||
|
|
||||||
## Autonomous Execution
|
## Autonomous Execution
|
||||||
|
|
||||||
- **Do not ask clarifying questions.** The implementation plan is the spec. If something is ambiguous, make a reasonable decision, leave a `// TODO:` comment, and keep going.
|
- **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 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 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.
|
- **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 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.
|
- **If you need a dependency, add it to pubspec.yaml**, run pub get, and continue.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
- Drift requires code generation — run `dart run build_runner build` after any table change or it won't compile.
|
- 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.
|
- 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` and `AppDelegate.swift`.
|
- `flutter_local_notifications` needs platform-specific init in `MainActivity.kt` (Android) and `AppDelegate.swift` (iOS) in addition to the Dart-side `NotificationService.initialize()`.
|
||||||
- RevenueCat needs platform-specific setup in both `android/` and `ios/` — follow their Flutter quickstart.
|
- 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.
|
||||||
- Free tier limits (3 invoices/month, 2 clients) are enforced at the app layer, not the database.
|
- 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user