docs: complete project research

This commit is contained in:
2026-03-15 18:37:00 +01:00
parent 1a4223aff5
commit aa4c2ab817
5 changed files with 1287 additions and 0 deletions

View File

@@ -0,0 +1,389 @@
# Architecture Research
**Domain:** Local-first Flutter household chore management app
**Researched:** 2026-03-15
**Confidence:** HIGH (cross-verified: official Flutter docs, CodeWithAndrea, Drift official docs, multiple community templates)
## Standard Architecture
### System Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ │
│ │ Screens │ │ Widgets │ │ Notifiers │ │ Providers│ │
│ │(RoomList, │ │(TaskCard, │ │(AsyncNotif │ │(wiring │ │
│ │ DailyPlan) │ │ RoomCard) │ │ -erProvider│ │ DI) │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └────┬─────┘ │
└────────┼───────────────┼───────────────┼───────────────┼────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ DOMAIN LAYER │
│ ┌────────────┐ ┌────────────┐ ┌────────────────────────────┐ │
│ │ Entities │ │ Repository │ │ Use Cases │ │
│ │(Room,Task, │ │ Interfaces │ │(CompleteTask, GetDailyPlan,│ │
│ │ Completion)│ │(abstract) │ │ ScheduleNextDue) │ │
│ └────────────┘ └────────────┘ └────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ DATA LAYER │
│ ┌───────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ Drift DAOs │ │ Repository │ │ Notification Data │ │
│ │(RoomDao, │ │ Impls │ │ Source │ │
│ │ TaskDao, │ │(RoomRepo, │ │(flutter_local_ │ │
│ │ CompletionDao)│ │ TaskRepo) │ │ notifications) │ │
│ └───────┬───────┘ └──────┬───────┘ └─────────┬─────────┘ │
└──────────┼─────────────────┼─────────────────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ AppDatabase (Drift / SQLite) │ │
│ │ Tables: rooms | tasks | task_completions | templates │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### Component Responsibilities
| Component | Responsibility | Typical Implementation |
|-----------|----------------|------------------------|
| Screen | Renders a full page; reads Riverpod providers | `ConsumerWidget` or `ConsumerStatefulWidget` |
| Widget | Reusable UI component; dumb or lightly reactive | Stateless/Consumer widget |
| AsyncNotifier | Manages async state for a feature; exposes methods for mutations | `AsyncNotifier<T>` subclass with `@riverpod` annotation |
| StreamProvider | Exposes a reactive Drift `.watch()` stream to the UI | `@riverpod Stream<List<T>> ...` |
| Entity | Immutable business object; no framework deps | Plain Dart class, often `@freezed` |
| Repository interface | Defines data contract; domain stays unaware of SQLite | Abstract Dart class |
| Repository impl | Implements interface using Drift DAOs; translates DB rows to entities | Concrete class in `data/` |
| DAO | Type-safe SQL operations for one table group | `@DriftAccessor` class |
| AppDatabase | Drift database root; holds all tables and registers DAOs | `@DriftDatabase` extending `_$AppDatabase` |
| NotificationService | Schedules/cancels local OS notifications | Wraps `flutter_local_notifications`; injected via Riverpod |
## Recommended Project Structure
```
lib/
├── core/
│ ├── database/
│ │ ├── app_database.dart # @DriftDatabase — root DB class
│ │ ├── app_database.g.dart # Generated by build_runner
│ │ └── database_provider.dart # Riverpod Provider<AppDatabase>
│ ├── notifications/
│ │ ├── notification_service.dart # Wraps flutter_local_notifications
│ │ └── notification_provider.dart # Riverpod provider for service
│ ├── theme/
│ │ └── app_theme.dart # Material 3 color scheme, typography
│ └── l10n/
│ └── app_de.arb # German strings
├── features/
│ ├── rooms/
│ │ ├── data/
│ │ │ ├── room_dao.dart # @DriftAccessor for rooms table
│ │ │ ├── room_dao.g.dart
│ │ │ └── room_repository_impl.dart
│ │ ├── domain/
│ │ │ ├── room.dart # Entity (Freezed)
│ │ │ └── room_repository.dart # Abstract interface
│ │ └── presentation/
│ │ ├── rooms_screen.dart
│ │ ├── room_detail_screen.dart
│ │ ├── rooms_provider.dart # StreamProvider watching rooms
│ │ └── widgets/
│ │ └── room_card.dart
│ │
│ ├── tasks/
│ │ ├── data/
│ │ │ ├── task_dao.dart
│ │ │ ├── task_dao.g.dart
│ │ │ └── task_repository_impl.dart
│ │ ├── domain/
│ │ │ ├── task.dart # Entity (Freezed)
│ │ │ ├── task_repository.dart
│ │ │ └── scheduling_service.dart # due-date recurrence logic
│ │ └── presentation/
│ │ ├── task_list_screen.dart
│ │ ├── task_form_screen.dart
│ │ ├── tasks_provider.dart
│ │ └── widgets/
│ │ └── task_card.dart
│ │
│ ├── daily_plan/
│ │ ├── domain/
│ │ │ └── daily_plan_service.dart # Query: overdue/today/upcoming
│ │ └── presentation/
│ │ ├── daily_plan_screen.dart
│ │ └── daily_plan_provider.dart
│ │
│ ├── completions/
│ │ ├── data/
│ │ │ ├── completion_dao.dart
│ │ │ └── completion_repository_impl.dart
│ │ └── domain/
│ │ ├── completion.dart
│ │ └── completion_repository.dart
│ │
│ └── templates/
│ ├── data/
│ │ └── bundled_templates.dart # Static template data (German)
│ └── domain/
│ └── task_template.dart
└── main.dart # ProviderScope root; bootstrap DB
```
### Structure Rationale
- **`core/`:** Houses exactly two cross-feature concerns — the shared `AppDatabase` instance and the `NotificationService`. These must be singletons shared across features, so they belong outside any feature folder. Theme and l10n are also here.
- **`features/`:** Each feature is fully self-contained with its own data/domain/presentation split. This lets you develop and test `tasks/` without touching `rooms/`, and makes the build order obvious — domain defines contracts first, data implements them, presentation consumes.
- **`data/` layer per feature:** Drift DAOs live here. One DAO per table group (rooms, tasks, completions). The repository impl translates between Drift-generated row objects and domain entities.
- **No `usecases/` folder initially:** For an app this size, use-case logic (e.g., "complete a task and compute next due date") lives in domain service classes or directly in `AsyncNotifier.build/methods`. Full use-case classes are appropriate at larger scale but add boilerplate overhead at MVP stage.
## Architectural Patterns
### Pattern 1: Reactive Drift Streams via StreamProvider
**What:** Drift's `.watch()` returns a `Stream<List<T>>` that emits on every relevant DB change. Expose this directly via a Riverpod `StreamProvider` so the UI rebuilds automatically without polling or manual refresh calls.
**When to use:** All read operations — room list, task list, daily plan. Anything the user needs to see stay current.
**Trade-offs:** Simple, reactive, zero caching complexity. Works perfectly for purely local data. Would require adjustment if a sync layer were added later.
**Example:**
```dart
// features/tasks/presentation/tasks_provider.dart
@riverpod
Stream<List<Task>> tasksByRoom(TasksByRoomRef ref, int roomId) {
final dao = ref.watch(databaseProvider).taskDao;
return dao.watchTasksByRoom(roomId).map(
(rows) => rows.map(Task.fromRow).toList(),
);
}
// features/tasks/data/task_dao.dart
@DriftAccessor(tables: [Tasks])
class TaskDao extends DatabaseAccessor<AppDatabase> with _$TaskDaoMixin {
TaskDao(super.db);
Stream<List<TaskData>> watchTasksByRoom(int roomId) =>
(select(tasks)..where((t) => t.roomId.equals(roomId))
..orderBy([(t) => OrderingTerm(expression: t.dueDate)]))
.watch();
}
```
### Pattern 2: AsyncNotifier for Mutations
**What:** Mutations (create, update, delete, complete) go through an `AsyncNotifier`. The notifier holds no derived state — it calls the repository and lets the `StreamProvider` propagate the DB change automatically.
**When to use:** Any write operation — creating a room, completing a task, editing task metadata.
**Trade-offs:** Clean separation between reads (StreamProvider) and writes (AsyncNotifier). The notifier is thin; no manual state synchronization needed because Drift streams handle it.
**Example:**
```dart
@riverpod
class TaskNotifier extends _$TaskNotifier {
@override
FutureOr<void> build() {}
Future<void> completeTask(int taskId) async {
final repo = ref.read(taskRepositoryProvider);
await repo.completeTask(taskId); // writes completion + updates dueDate
// No setState needed — Stream from watchTasksByRoom emits automatically
}
}
```
### Pattern 3: Single AppDatabase Singleton via Riverpod
**What:** One `Provider<AppDatabase>` at the application root. All DAOs are accessed as getters on this instance (`db.taskDao`, `db.roomDao`). The provider registers `ref.onDispose(db.close)` to clean up.
**When to use:** Always. Multiple DB instances will corrupt SQLite state.
**Trade-offs:** Simple and correct. The singleton approach works well for a single-device, offline app. Would need adjustment only if the schema were split across multiple databases (rare for apps this size).
**Example:**
```dart
// core/database/database_provider.dart
@riverpod
AppDatabase appDatabase(AppDatabaseRef ref) {
final db = AppDatabase();
ref.onDispose(db.close);
return db;
}
```
## Data Flow
### Read Flow (Reactive)
```
User opens screen
ConsumerWidget watches StreamProvider
StreamProvider subscribes to DAO .watch() stream
Drift executes SQL SELECT, returns Stream<List<Row>>
Row objects mapped to domain Entities in provider
Widget renders from AsyncValue<List<Entity>>
[Any DB write elsewhere]
Drift detects table change, emits new list to all watchers
Widget rebuilds automatically
```
### Write Flow (Mutation)
```
User taps "Mark Done"
Widget calls ref.read(taskNotifierProvider.notifier).completeTask(id)
AsyncNotifier calls TaskRepository.completeTask(id)
Repository impl calls TaskDao.insertCompletion() + TaskDao.updateNextDue()
Drift writes to SQLite
All active .watch() streams for affected tables emit updated data
Daily plan StreamProvider rebuilds automatically
UI reflects completion + new due date
```
### Notification Scheduling Flow
```
App startup / task completion
NotificationService.scheduleDaily(summaryTime)
flutter_local_notifications schedules OS alarm
(No Riverpod state involved — fire-and-forget side effect)
OS delivers notification at scheduled time
User taps notification → app opens to DailyPlanScreen
```
### Key Data Flows Summary
1. **Room list:** `AppDatabase.roomDao.watchAllRooms()``StreamProvider<List<Room>>``RoomsScreen`
2. **Daily plan:** `AppDatabase.taskDao.watchDueTasks(today)``StreamProvider<DailyPlan>``DailyPlanScreen` (sections: overdue / today / upcoming)
3. **Task completion:** `TaskNotifierProvider.completeTask()``TaskDao.insertCompletion() + updateNextDue()` → all watchers update
4. **Cleanliness indicator:** Derived in provider from overdue task count per room — computed from `watchTasksByRoom` stream, no separate table needed
5. **Template seeding:** On first launch, `main.dart` checks `AppDatabase` for empty tables and seeds from static `bundled_templates.dart` within a single transaction
## Scaling Considerations
This is a single-device offline app. Scaling means "works without degradation as data grows over months/years," not multi-user or distributed concerns.
| Scale | Architecture Adjustments |
|-------|--------------------------|
| < 500 tasks | No optimization needed — Drift handles it trivially |
| 5005,000 tasks | Add indexes on `due_date` and `room_id` in Drift table definitions (add from the start to avoid a migration later) |
| 5,000+ tasks | Paginate daily plan queries; add `LIMIT/OFFSET` or cursor-based pagination in DAOs |
| Task history growth | Completion log can grow unbounded. Archive completions older than 1 year in a separate table or apply `DELETE WHERE created_at < X` on startup |
### Scaling Priorities
1. **First bottleneck:** Drift `.watch()` queries that return very large lists — fix with `LIMIT` and proper indexes
2. **Second bottleneck:** Notification scheduling on a large task set — fix by only scheduling the next N upcoming tasks rather than all tasks
## Anti-Patterns
### Anti-Pattern 1: Accessing AppDatabase directly in widgets
**What people do:** `ref.watch(appDatabaseProvider).taskDao.watchAllTasks()` inside a `build()` method.
**Why it's wrong:** Creates tight coupling between UI and infrastructure. Bypasses the repository pattern. Makes the widget untestable without a real database. Breaks the dependency rule — presentation should not know about Drift.
**Do this instead:** Always go through a StreamProvider or AsyncNotifier that wraps the DAO call. Widgets only `ref.watch(tasksByRoomProvider(roomId))`.
### Anti-Pattern 2: Multiple AppDatabase instances
**What people do:** Calling `AppDatabase()` in multiple places — e.g., inside each DAO file or feature-level provider.
**Why it's wrong:** SQLite with multiple connections to the same file causes locking errors and data corruption. Drift will throw at runtime.
**Do this instead:** One `@riverpod AppDatabase appDatabase(...)` provider at the app root. All DAOs are accessed as getters on `ref.watch(appDatabaseProvider)`.
### Anti-Pattern 3: Storing computed state in the database
**What people do:** Adding a `cleanliness_score` column that is updated on every task completion.
**Why it's wrong:** Derived data that can be computed from existing rows should never be stored. It creates consistency bugs (score can drift out of sync with actual task states). It adds unnecessary write operations.
**Do this instead:** Compute cleanliness score in a Riverpod provider that derives from the `watchTasksByRoom` stream. Pure computation, always consistent.
### Anti-Pattern 4: Using `ref.read` in widget `build()` for streams
**What people do:** `final tasks = ref.read(tasksProvider)` inside `build()` to avoid rebuilds.
**Why it's wrong:** `ref.read` does not subscribe — the widget will not update when the stream emits new data. This defeats the entire point of reactive streams.
**Do this instead:** Always `ref.watch(tasksProvider)` in `build()`. To avoid excessive rebuilds, use `ref.select()` or split into smaller widgets.
### Anti-Pattern 5: Business logic in Drift DAOs
**What people do:** Putting "complete task and compute next due date" entirely inside the DAO.
**Why it's wrong:** DAOs should be thin SQL wrappers. Business rules (recurrence calculation, overdue logic) belong in the domain layer — `SchedulingService` or within `TaskRepository`. DAOs that contain business logic are harder to test and break the Clean Architecture dependency rule.
**Do this instead:** DAO handles SQL. Repository impl calls DAO + domain service. Domain service contains recurrence math.
## Integration Points
### Internal Boundaries
| Boundary | Communication | Notes |
|----------|---------------|-------|
| Presentation -> Domain | Riverpod providers (read/watch repository interfaces) | Presentation never imports from `data/` directly |
| Domain -> Data | Repository interface (abstract class) | Domain defines interface; data implements it |
| Data -> Infrastructure | Drift DAO methods | DAO is the only thing that knows about table/column names |
| Notifications -> Domain | `NotificationService` injected via Riverpod; called from `TaskNotifier` after writes | Notifications are a side effect, not part of state |
| Templates -> Database | Seeded once on first launch via `AppDatabase.transaction()` | Static data; not a runtime concern |
### Build Order (Dependency Sequence)
Components must be built in dependency order. Later steps depend on earlier ones:
1. **AppDatabase schema** — tables, migrations; everything else depends on this
2. **DAOs** — generated from table definitions; needed by repository impls
3. **Domain entities + repository interfaces** — pure Dart; no framework deps
4. **Repository implementations** — depend on DAOs and domain entities
5. **Riverpod providers** — wire database and repositories; depend on both
6. **Domain services** — scheduling, daily plan computation; depend on entities
7. **Feature notifiers** — depend on repository providers and domain services
8. **Screens and widgets** — depend on notifiers and stream providers
9. **NotificationService** — can be built any time after step 1; integrated as a side effect in step 7
10. **Template seeding** — runs at app startup after AppDatabase is available
## Sources
- [Flutter App Architecture with Riverpod: An Introduction — CodeWithAndrea](https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/)
- [Persistent storage architecture: SQL — Flutter official docs](https://docs.flutter.dev/app-architecture/design-patterns/sql)
- [Offline-first support — Flutter official docs](https://docs.flutter.dev/app-architecture/design-patterns/offline-first)
- [DAOs — Drift official documentation](https://drift.simonbinder.eu/dart_api/daos/)
- [Building Local-First Flutter Apps with Riverpod, Drift, and PowerSync — Dinko Marinac](https://dinkomarinac.dev/blog/building-local-first-flutter-apps-with-riverpod-drift-and-powersync/)
- [Flutter Riverpod Clean Architecture Template — ssoad (GitHub)](https://github.com/ssoad/flutter_riverpod_clean_architecture)
- [Integrating Local Databases in Flutter Using Drift — vibe-studio.ai](https://vibe-studio.ai/insights/integrating-local-databases-in-flutter-using-drift)
- [State Management with Riverpod — sample_drift_app (DeepWiki)](https://deepwiki.com/h-enoki/sample_drift_app/5.1-state-management-with-riverpod)
---
*Architecture research for: Local-first Flutter household chore management app (HouseHoldKeaper)*
*Researched: 2026-03-15*

View File

@@ -0,0 +1,227 @@
# Feature Research
**Domain:** Household chore / cleaning schedule management app (local-first, Android)
**Researched:** 2026-03-15
**Confidence:** MEDIUM-HIGH (competitor apps analyzed directly; user pain points verified from multiple sources)
---
## Feature Landscape
### Table Stakes (Users Expect These)
Features users assume exist. Missing these = product feels incomplete or broken.
| Feature | Why Expected | Complexity | Notes |
|---------|--------------|------------|-------|
| Room-based organization | Every competitor (Tody, Sweepy, BeTidy, OurHome) uses rooms as the primary organizing unit. Users mentally group chores by room, not by abstract category. | LOW | Rooms need icon support at minimum; photos are optional |
| Task CRUD with recurrence intervals | Core loop of any chore app — create a task, set how often it repeats, mark it done. Without this nothing else works. | MEDIUM | Intervals: daily, every N days, weekly, monthly, seasonal. "Every N days" is more flexible than day-of-week pickers. |
| Auto-scheduling next due date after completion | After marking done, the next due date must recalculate automatically. Without this users must manually manage dates — defeats the purpose. | LOW | Next due = completion date + interval |
| Visual "today" view with overdue/upcoming sections | All competitors surface what needs doing today. Overdue items must be visually distinct (users feel guilt-free pressure, not anxiety). | MEDIUM | Three-band layout: Overdue / Due Today / Upcoming |
| Task completion (mark done) | Obvious but critical — the satisfying tap that completes a task and updates the schedule. | LOW | Should feel responsive and immediate |
| Daily notification / reminder | Every competitor offers push notifications for daily task reminders. Users who don't get reminded forget to open the app. | LOW | Single daily summary notification is enough; per-task reminders are scope creep for MVP |
| Bundled task templates per room type | Users don't know what tasks to create for a new "Bathroom" room. Templates eliminate blank-slate anxiety and time-to-value. BeTidy and Sweepy both do this. | MEDIUM | Curate ~5-10 tasks per room type; templates should be pre-seeded, not user-created |
| Light/dark theme support | Material 3 baseline expectation on Android. Users switching themes see a broken experience if unsupported. | LOW | System-default follow; explicit toggle is a v2 nicety |
| Task sorting (at minimum by due date) | Overcrowded task lists are unreadable without sort. Due date sort is the baseline; additional sorts are differentiators. | LOW | Due date sort must ship at MVP; alphabetical/interval/effort sorts are v1.x |
---
### Differentiators (Competitive Advantage)
Features that set the product apart from competitors. Not table stakes, but provide meaningful value aligned with the core value proposition.
| Feature | Value Proposition | Complexity | Notes |
|---------|-------------------|------------|-------|
| Cleanliness indicator per room | Tody pioneered the green-to-red "dirtiness" visual. It gives users an at-a-glance health check without opening every room. HouseHoldKeaper's local-first, calm design can do this without cloud dependency. | MEDIUM | Derived from overdue task ratio; no network required. Formula: ratio of overdue tasks vs total tasks per room |
| Task history / completion log | Most apps don't surface history prominently. Users with OCD tendencies and couples wanting accountability ("did anyone clean the bathroom last week?") want a log. Home Tasker and HomeHero charge for this. | MEDIUM | Store completion timestamps in SQLite; display as scrollable log per task. Drift makes this straightforward. |
| Zero account, zero cloud, zero data leaving device | No competitor offers this by default. Every top app (Tody, Sweepy, BeTidy, OurHome) requires an account or subscription for sync. Privacy-conscious users (the explicit target) have no good option today. | LOW (by design) | This is a design constraint, not a feature to build — but it IS a selling point that sets the app apart fundamentally |
| Calm, non-gamified UI | Sweepy and OurHome lean heavily into points, leaderboards, and coins. Tody's "Dusty" mascot is polarizing. Research shows gamification causes long-term drop-off; calm productivity has a distinct audience. | LOW | Material 3 muted palette (greens, warm grays, gentle blues) — the design philosophy IS the differentiator |
| Task effort/duration tagging | Knowing a task takes 5 minutes vs 45 minutes lets users pick tasks that fit available time. Only a few apps offer this (Today app does time-tracking). Enables "I have 20 minutes — what can I do?" filtering. | MEDIUM | Simple enum (quick/medium/long) or numeric minutes; used in sorting and future filtering |
| One-time project tasks alongside recurring chores | BeTidy supports this; most others don't. "Paint the hallway" is not a recurring chore but belongs in the household task system. Supports the core value: one place for everything household. | MEDIUM | Distinguish recurring vs one-time tasks; one-time tasks disappear from daily view after completion |
| Vacation / pause mode | Users going on holiday return to a crushing list of overdue tasks. Pause mode freezes due dates and resumes from departure date. Spix and Daily Cleaning Routines both have this. | MEDIUM | Store a "paused until" date; reschedule all tasks on resume |
---
### Anti-Features (Commonly Requested, Often Problematic)
Features that seem desirable but create complexity without proportional value — or contradict the project's core values.
| Feature | Why Requested | Why Problematic | Alternative |
|---------|---------------|-----------------|-------------|
| Cloud sync / multi-device sync | Partners want tasks to sync across two phones | Requires backend infrastructure, accounts, auth, server costs, and ongoing maintenance — directly contradicts zero-backend constraint. Also kills privacy angle. | Single shared Android device (stated use case). Future: self-hosted sync as opt-in v2+ |
| Family profiles / user accounts | OurHome and BeTidy offer per-person task assignment | Requires user model, login, profile management — significant complexity for a two-person shared-device app. Adds friction without benefit when both users share one phone. | No profiles for MVP. Future: simple "assigned to" text label on tasks |
| Gamification (points, leaderboards, coins) | Sweepy/OurHome use this for engagement | Research (MIT Technology Review, 2022) shows gamification embeds unequal labor dynamics and causes "app burnout." Contradicts calm productivity positioning. Short-term engagement, long-term churn. | Cleanliness indicator provides visual feedback without coercion |
| In-app purchases / subscription | Monetization | Project is free-forever by design; subscriptions create "paywalled features" resentment (the #2 complaint across all chore app reviews) | Free, no IAP. Publish open source if desired. |
| Statistics & insights dashboard | Power users want historical graphs | High complexity UI; requires substantial history data to be meaningful; distracts from core loop for MVP. BeTidy gates this behind Pro. | Task history log satisfies immediate need; dashboards deferred to v2.0 |
| AI-powered task suggestions (camera scan, etc.) | Sweepy 2025 added camera-to-task AI | Requires network / on-device ML model; adds significant complexity; overkill for a personal app with curated templates | Bundled room templates eliminate blank-slate problem without AI |
| Real-time cross-device sync | Couple wants independent phones to stay in sync | Requires network, conflict resolution, operational infrastructure — the entire anti-pattern for this app | Shared device workflow; defer to self-hosted CouchDB/SQLite sync in v2+ |
| Per-task reminders (individual push notifications) | Users want reminder at specific time for each task | Notification fatigue; permission management complexity; most users report turning off per-task notifications within a week. Daily summary is more effective for habit formation. | Single daily summary notification |
| English localization for MVP | Broader audience reach | Adds localization infrastructure, string management, and review complexity before the app is even validated. Ships slower, validates nothing about the core product. | Ship German-only; add i18n infrastructure in v1.1 |
| Tablet-optimized layout | Larger screen = better experience | Responsive layout engineering is non-trivial in Flutter for adaptive breakpoints; primary use case is phone | Defer to v1.1; phone layout is sufficient for stated use case |
---
## Feature Dependencies
```
Room CRUD
└──requires──> Task CRUD (tasks belong to rooms)
└──requires──> Auto-scheduling (tasks need next-due logic)
└──requires──> Daily plan view (needs due dates to sort)
└──requires──> Task completion (mark-done triggers reschedule)
Task templates
└──requires──> Room CRUD (templates pre-populate a room's tasks)
Cleanliness indicator
└──requires──> Task CRUD + Auto-scheduling (indicator derives from overdue ratio)
└──enhances──> Daily plan view (room-level health visible from plan)
Task history
└──requires──> Task completion (completion events are what get logged)
└──enhances──> Cleanliness indicator (can show trend, not just current state)
Daily notification
└──requires──> Auto-scheduling (needs due dates to determine what to notify about)
Task effort tagging
└──enhances──> Task sorting (sort by effort as one option)
└──enhances──> Daily plan view (filter by available time — future)
Vacation / pause mode
└──requires──> Auto-scheduling (pause freezes the scheduler)
One-time project tasks
└──requires──> Task CRUD (same data model, different recurrence setting)
└──conflicts──> Auto-scheduling (one-time tasks must NOT reschedule after completion)
```
### Dependency Notes
- **Room CRUD requires Task CRUD:** Rooms are containers; without tasks, rooms are inert. Both must ship together.
- **Auto-scheduling requires Task CRUD:** The scheduler reads interval and last-completion-date from the task record; the task must exist first.
- **Daily plan view requires Auto-scheduling:** The plan view is entirely driven by computed due dates; without the scheduler there is nothing to show.
- **Task templates require Room CRUD:** Templates are seeded into a room at creation time; rooms must exist first.
- **Cleanliness indicator requires Task CRUD + Auto-scheduling:** The indicator is a derived metric (count of overdue tasks / total tasks per room). Cannot be computed without tasks and due dates.
- **One-time tasks conflict with auto-scheduling:** One-time tasks must be detected and excluded from the scheduler loop; they complete and stay completed. This is a flag on the task model, not a separate feature — but it must be accounted for in the scheduler logic.
---
## MVP Definition
### Launch With (v1)
Minimum viable product — what's needed to validate the core "see what needs doing today, mark it done, trust the app to schedule the next one" loop.
- [ ] Room CRUD with icons — without rooms there is no organizing structure
- [ ] Task CRUD with recurrence intervals (daily / every N days / weekly / monthly / seasonal) — the core scheduling primitive
- [ ] Auto-scheduling of next due date on task completion — the "fire and forget" reliability promise
- [ ] Daily plan view (Overdue / Today / Upcoming sections) — the primary answer to "what do I do now?"
- [ ] Task completion action — the satisfying feedback loop closure
- [ ] Bundled task templates per room type (German language) — eliminates setup friction for new users
- [ ] Daily summary notification — keeps users returning without per-task notification complexity
- [ ] Light/dark theme (system-default) — Material 3 baseline expectation
- [ ] Cleanliness indicator per room — visible at-a-glance health check; derived from overdue ratio, zero extra complexity
- [ ] Task sorting by due date — minimum viable list usability
### Add After Validation (v1.x)
Features to add once the core scheduling loop is confirmed working and users are returning daily.
- [ ] Task history / completion log — users will ask "when did I last do this?"; add once task completion data has accumulated
- [ ] Vacation / pause mode — first travel use case will surface this need
- [ ] Data export/import (JSON) — backup and migration; already noted in PROJECT.md as v1.1
- [ ] Additional sort options (alphabetical, interval, effort) — power user usability improvement
- [ ] Task effort/duration tagging — enables time-based filtering ("I have 20 minutes")
- [ ] English localization — widen audience after German MVP validates the concept
- [ ] One-time project task type — "Paint the hallway" use case; simple model extension
### Future Consideration (v2+)
Features to defer until product-market fit is established.
- [ ] Statistics & insights dashboard — requires historical data volume to be meaningful; high UI complexity
- [ ] Onboarding wizard — reduces cold-start friction at scale; overkill for initial personal use
- [ ] Custom accent color picker — personalization; non-core
- [ ] Tablet-optimized layout — secondary device form factor
- [ ] Self-hosted sync (CouchDB / SQLite replication) — multi-device without cloud compromise; technically interesting but complex
- [ ] "Assigned to" label on tasks — minimal multi-user support without full profiles
---
## Feature Prioritization Matrix
| Feature | User Value | Implementation Cost | Priority |
|---------|------------|---------------------|----------|
| Room CRUD with icons | HIGH | LOW | P1 |
| Task CRUD with recurrence | HIGH | MEDIUM | P1 |
| Auto-scheduling next due date | HIGH | LOW | P1 |
| Daily plan view (3-band layout) | HIGH | MEDIUM | P1 |
| Task completion action | HIGH | LOW | P1 |
| Bundled task templates | HIGH | MEDIUM | P1 |
| Daily summary notification | HIGH | LOW | P1 |
| Light/dark theme | MEDIUM | LOW | P1 |
| Cleanliness indicator per room | HIGH | LOW | P1 |
| Task sorting by due date | MEDIUM | LOW | P1 |
| Task history / completion log | MEDIUM | LOW | P2 |
| Vacation / pause mode | MEDIUM | MEDIUM | P2 |
| Task effort tagging | MEDIUM | LOW | P2 |
| Additional sort options | LOW | LOW | P2 |
| One-time project tasks | MEDIUM | MEDIUM | P2 |
| Statistics dashboard | LOW | HIGH | P3 |
| Onboarding wizard | LOW | MEDIUM | P3 |
| Custom accent color | LOW | LOW | P3 |
| Tablet layout | LOW | HIGH | P3 |
| Self-hosted sync | MEDIUM | HIGH | P3 |
**Priority key:**
- P1: Must have for launch
- P2: Should have, add when possible
- P3: Nice to have, future consideration
---
## Competitor Feature Analysis
| Feature | Tody | Sweepy | BeTidy | OurHome | Home Routines | HouseHoldKeaper |
|---------|------|--------|--------|---------|---------------|-----------------|
| Room-based organization | Yes | Yes | Yes | Partial (categories) | Yes (Focus Zones) | Yes |
| Visual cleanliness indicator | Yes (green→red bar) | Yes (Cleanliness Meter) | No | No | No | Yes (derived metric) |
| Recurring tasks / intervals | Yes | Yes | Yes | Yes | Yes | Yes |
| Auto-schedule on completion | Yes | Yes | Yes | Yes | Yes | Yes |
| Daily plan view | Yes | Yes | Yes | Yes | Yes | Yes |
| Task templates | Basic | Yes (suggestions) | Yes | Partial | No | Yes (bundled per room type) |
| Task history / log | No (premium only hints) | Partial | No | No | No | Yes (v1.x) |
| Gamification (points/coins) | Partial (Dusty mascot) | Strong | No | Strong | No | No (deliberate) |
| Family profiles / sharing | Yes (premium) | Yes (premium) | Yes | Yes (free) | No | No (deliberate) |
| Cloud sync | Yes (premium) | Yes (premium) | Yes | Yes | No | No (deliberate) |
| Requires account | Yes | Yes | Yes | Yes | No | No |
| Offline / local-first | Partial | No | No | No | Yes | Yes (100%) |
| Vacation / pause mode | Yes | No | No | No | Partial | v1.x |
| Focus timer (Pomodoro) | Yes | No | No | No | Yes | No |
| One-time project tasks | Partial | No | Yes | No | No | v1.x |
| Free tier | Partial | Partial | Partial | Yes (full) | No ($4.99) | Yes (fully free) |
| No subscription / IAP | No | No | No | Yes | Yes | Yes |
| German language UI | No | No | Yes | No | No | Yes (MVP-only) |
| Privacy / no tracking | No | No | No | No | No | Yes |
**Key gap this app fills:** The market has no room-based, local-first, account-free, calm (non-gamified), fully private chore management app. OurHome is free but cloud-dependent. Home Routines is local but iOS-only, not room-based, and costs money. Tody is the closest UX model but requires subscription for multi-device and is not privacy-first.
---
## Sources
- BeTidy app (Google Play / App Store): https://betidy.io/en/
- Tody app review 2025: https://www.tidied.app/blog/tody-app-review
- Sweepy app review 2025: https://www.tidied.app/blog/sweepy-app-review
- Best chore apps 2025 comparison: https://thetoday.app/blog/chore-apps-the-best-5-house-chore-apps-reviewed/
- Chore apps user pain points (MIT Technology Review): https://www.technologyreview.com/2022/05/10/1051954/chore-apps/
- Tody app (productivity.directory): https://productivity.directory/tody
- Sweepy app (Google Play): https://play.google.com/store/apps/details?id=app.sweepy.sweepy
- OurHome app review: https://noobie.com/ourhome-app-review/
- Home Routines app: https://www.homeroutines.com/
- Best free chore apps 2025 (mychoreboard): https://www.mychoreboard.com/blog/best-free-chore-apps-2025/
---
*Feature research for: local-first household chore management app (Android, Flutter)*
*Researched: 2026-03-15*

View File

@@ -0,0 +1,258 @@
# Pitfalls Research
**Domain:** Local-first Flutter household chore management app (Android, Riverpod + Drift)
**Researched:** 2026-03-15
**Confidence:** HIGH (Riverpod/Drift specifics), MEDIUM (scheduling edge cases), HIGH (Android notification permissions)
---
## Critical Pitfalls
### Pitfall 1: Drift Schema Changes Without Incrementing `schemaVersion`
**What goes wrong:**
A developer modifies a table definition (adds a column, renames a field, adds a new table) but forgets to increment `schemaVersion` and write a corresponding `onUpgrade` migration. On fresh installs, `onCreate` runs cleanly and everything works. On existing installs, the app opens against the old schema — queries silently return wrong data or crash with column-not-found errors. This is especially dangerous because it only surfaces after shipping an update to a real device.
**Why it happens:**
Drift's code generation regenerates Dart classes correctly based on the new table definition, so the app compiles and runs fine in development (developer always uses a fresh database). The migration gap is invisible until the app is installed over an older version.
**How to avoid:**
- Before making any schema change, run `dart run drift_dev make-migrations` to snapshot the current schema. Drift generates a migration test file you can run to verify correctness.
- Treat `schemaVersion` as a checklist item: every PR that touches a table definition must bump the version and add a `stepByStep` migration block.
- Use Drift's `stepByStep` API rather than a raw `if (from < N)` block — it generates per-step migration scaffolding that reduces errors.
- Test migrations with `dart run drift_dev schema verify` against the generated schema snapshots.
**Warning signs:**
- A table definition file changed in a commit but `schemaVersion` did not change.
- Tests pass on CI (fresh DB) but crash reports appear from field users after an update.
- `SqliteException: no such column: X` errors in crash logs.
**Phase to address:** Foundation phase (database setup). Establish the `make-migrations` workflow before writing the first table.
---
### Pitfall 2: "Next Due Date" Calculated from Wall-Clock `DateTime.now()` Without Timezone Anchoring
**What goes wrong:**
Recurring tasks use an interval (e.g., "every 7 days"). When a task is marked complete, the next due date is computed as `completionTime + Duration(days: interval)`. Because `DateTime.now()` returns UTC or local time depending on context, and because the notion of "today" is timezone-sensitive, two bugs emerge:
1. **Completion near midnight:** A task completed at 11:58 PM lands on a different calendar day than one completed at 12:02 AM. The next due date shifts by one day unpredictably.
2. **"Every N days" vs. "after completion":** Without a clear policy, the schedule drifts — a weekly vacuum scheduled for Monday slowly becomes a Wednesday vacuum because the user always completes it a day late.
**Why it happens:**
Developers store `DateTime` values directly from `DateTime.now()` without thinking about whether "due today" means "due before end of local calendar day" or "due within 24 hours." SQLite stores timestamps as integers (Unix epoch) — no timezone metadata is preserved.
**How to avoid:**
- Store all due dates as `Date` (calendar day only — year/month/day), not `DateTime`. For a chore app, a task is due "on a day," not "at a specific second."
- Use `DateTime.now().toLocal()` explicitly and strip time components when computing next due dates: `DateTime(now.year, now.month, now.day + interval)`.
- Define a policy at project start: "every N days from last completion date" (rolling) vs. "every N days from original start date" (fixed anchor). HouseHoldKeaper's core value — "trust the app to schedule the next occurrence" — implies rolling is correct, but document this explicitly.
- Store `lastCompletedDate` as a calendar date, not a timestamp, so the next due date calculation is always date arithmetic, never time arithmetic.
**Warning signs:**
- Due dates drifting week-by-week when a user is consistently slightly early or late on completions.
- "Overdue" tasks appearing that were completed the previous evening.
- Unit tests for due date logic only testing noon completions, never midnight edge cases.
**Phase to address:** Core task/scheduling phase. Write unit tests for due date calculation before wiring it to the UI.
---
### Pitfall 3: Android Notification Permissions Not Requested at Runtime (API 33+)
**What goes wrong:**
The app targets Android 13+ (API 33). `POST_NOTIFICATIONS` is a runtime permission — notifications are **off by default** for new installs. The app ships, the daily summary notification never fires, and users see nothing without knowing why. Additionally, `SCHEDULE_EXACT_ALARM` is denied by default on API 33+, so scheduled notifications (even if the permission is declared in the manifest) silently fail to fire.
**Why it happens:**
Developers coming from pre-API 33 experience assume notification permission is implicitly granted. The manifest declaration is necessary but not sufficient — a runtime `requestPermission()` call is required before `POST_NOTIFICATIONS` works on API 33+.
**How to avoid:**
- Declare `POST_NOTIFICATIONS`, `SCHEDULE_EXACT_ALARM`, and `RECEIVE_BOOT_COMPLETED` in `AndroidManifest.xml`.
- Use `flutter_local_notifications`'s `.requestNotificationsPermission()` method on Android at an appropriate moment (first launch or when the user enables notifications in settings).
- Always call `canScheduleExactAlarms()` before scheduling; if denied, guide the user to Settings → Special app access → Alarms.
- Use `AndroidScheduleMode.exactAllowWhileIdle` for all scheduled notifications to ensure delivery during Doze mode.
- Call `tz.initializeTimeZones()` at app startup — missing this is a silent bug that causes scheduled notification times to be wrong.
- Register `ScheduledNotificationBootReceiver` in the manifest to reschedule notifications after device reboot.
**Warning signs:**
- Notification permission prompt never appears during testing on a real API 33 device.
- Scheduled test notifications arrive correctly on emulator (which has fewer battery restrictions) but not on a physical device.
- `flutter_local_notifications` returns success from the schedule call but no notification fires.
**Phase to address:** Notifications phase. Test on a physical API 33+ Android device, not just the emulator, before marking notifications done.
---
### Pitfall 4: Riverpod `ref.watch` / `ref.listen` Used Outside `build`
**What goes wrong:**
Calling `ref.watch(someProvider)` inside `initState`, a button callback, or an async function causes either a runtime exception ("Cannot call watch outside a build-scope") or silent stale reads (the widget never rebuilds when the watched provider changes). Similarly, calling `ref.listen` inside `initState` instead of using `ref.listenManual` results in the listener being lost after the first rebuild.
**Why it happens:**
The distinction between `ref.watch` (for reactive rebuilds inside `build`) and `ref.read` (for one-time reads in callbacks) is non-obvious to Riverpod newcomers. The PROJECT.md acknowledges the developer is new to Drift — this likely extends to Riverpod patterns as well.
**How to avoid:**
- Rule of thumb: `ref.watch` **only** inside `build()`. `ref.read` in callbacks, `initState`, event handlers. `ref.listen` in `build()` for side effects (showing SnackBars). `ref.listenManual` in `initState` or outside build.
- Enable `riverpod_lint` rules — they catch `ref.watch` outside `build` at analysis time, before runtime.
- Prefer `@riverpod` code generation (riverpod_generator) over manually constructing providers — the annotation API reduces the surface area for these misuse patterns.
- Do not call `ref.read(autoDisposeProvider)` in `initState` to "warm up" a provider — the auto-dispose mechanism may discard the state before any widget watches it.
**Warning signs:**
- "Cannot use ref functions after the widget was disposed" exceptions in logs.
- UI not updating when underlying provider state changes, even though data changes are confirmed in the database.
- `ConsumerStatefulWidget` screens that call `ref.watch` in `initState`.
**Phase to address:** Foundation phase (project structure and state management setup). Establish linting rules before writing feature code.
---
### Pitfall 5: `autoDispose` Providers Losing State on Screen Navigation
**What goes wrong:**
Riverpod's code-generation (`@riverpod`) enables `autoDispose` by default. When a user navigates away from the task list and returns, the provider state is discarded and rebuilt from scratch — triggering a full database reload, causing visible loading flicker, and losing any in-flight UI state (e.g., a sort selection, scroll position). For a household app used daily, this is a recurring annoyance.
**Why it happens:**
`autoDispose` disposes provider state when no listeners remain (i.e., when the widget leaves the tree). Developers using code generation often don't realize `autoDispose` is the default and that persistent UI state requires opt-in `keepAlive`.
**How to avoid:**
- Use `@Riverpod(keepAlive: true)` for long-lived app-wide state: room list, task list, daily plan view. These should persist for the entire app session.
- Use default `autoDispose` only for transient UI state that should be discarded (e.g., form state for a "new task" dialog).
- For async providers that load database content, use `ref.keepAlive()` after the first successful load to prevent re-querying on every navigation.
- Drift's `.watch()` streams already provide reactive updates — no need to aggressively dispose and recreate the Riverpod layer; let Drift push updates.
**Warning signs:**
- Noticeable loading flash every time the user returns to the main screen.
- Database queries firing more often than expected (visible in logs).
- Form state (partially filled "edit task" screen) resetting if the user briefly leaves.
**Phase to address:** Core UI phase. Define provider lifetime policy at architecture setup before building feature providers.
---
## Technical Debt Patterns
Shortcuts that seem reasonable but create long-term problems.
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|----------|-------------------|----------------|-----------------|
| Storing due dates as `DateTime` (with time) instead of `Date` (date only) | Less refactoring of existing patterns | Midnight edge cases, overdue logic bugs, timezone drift | Never — use date-only from day one |
| Skipping Drift `make-migrations` workflow for early schemas | Faster iteration in early development | First schema change on a shipped app causes data loss | Only acceptable before first public release; must adopt before any beta |
| Hardcoding color values instead of using `ColorScheme.fromSeed()` | Faster during prototyping | Broken dark mode, inconsistent tonal palette, future redesign pain | Prototyping only; replace before first feature-complete build |
| Using `ref.read` everywhere instead of `ref.watch` | No accidental rebuilds | UI never updates reactively; state bugs that look like data bugs | Never in production widgets |
| Scheduling a notification for every future recurrence at task creation | Simpler code path | Hits Samsung's 500-alarm limit; stale notifications after task edits; manifest bloat | Never — schedule only the next occurrence |
| Skipping `riverpod_lint` to avoid setup overhead | Faster start | Silent provider lifecycle bugs accumulate; harder to catch without the linter | Never |
---
## Integration Gotchas
Common mistakes when integrating the core libraries.
| Integration | Common Mistake | Correct Approach |
|-------------|----------------|------------------|
| `flutter_local_notifications` + Android 13 | Declaring permission in manifest only | Also call `requestNotificationsPermission()` at runtime; check `canScheduleExactAlarms()` |
| `flutter_local_notifications` + scheduling | Using `DateTime.now()` without timezone init | Call `tz.initializeTimeZones()` at startup; use `TZDateTime` for all scheduled times |
| `flutter_local_notifications` + reboot | Not registering `ScheduledNotificationBootReceiver` | Register receiver in `AndroidManifest.xml`; reschedule on boot |
| Drift + `build_runner` | Running `build_runner build` once then forgetting | Use `build_runner watch` during development; always re-run after table changes |
| Drift + migrations | Adding columns without `addColumn()` migration | Always pair `schemaVersion` bump with explicit migration step; use `stepByStep` API |
| Riverpod + Drift | Watching a Drift `.watch()` stream inside a Riverpod `StreamProvider` | This is correct — but ensure the `StreamProvider` has `keepAlive: true` to avoid stream teardown on navigation |
| Riverpod + `@riverpod` codegen | Assuming `@riverpod` is equivalent to `@Riverpod(keepAlive: false)` | It is — explicitly add `keepAlive: true` for providers that must survive navigation |
---
## Performance Traps
Patterns that work at small scale but cause UX issues as data grows.
| Trap | Symptoms | Prevention | When It Breaks |
|------|----------|------------|----------------|
| Loading all tasks into memory, then filtering in Dart | Fine with 20 tasks, slow with 200 | Push filter/sort logic into Drift SQL queries with `.where()` and `orderBy` | Around 100-200 tasks when filtering involves date math |
| Rebuilding the entire task list widget on any state change | Imperceptible with 10 items, janky with 50+ | Use `select` on providers to narrow rebuilds; use `ListView.builder` (never `Column` with mapped list) | 30-50 tasks in a scrollable list |
| Storing room photos as raw bytes in SQLite | Works for 1-2 photos, degrades quickly | Store photos as file paths in the document directory; keep only the path in the database | After 3-4 high-res room photos |
| Scheduling a daily notification with an exact `TZDateTime` far into the future | Works initially; notification becomes stale after task edits or completions | Schedule only the next occurrence; reschedule in the task completion handler | After the first task edit that changes the due date |
---
## UX Pitfalls
Common UX mistakes specific to recurring task / chore management apps.
| Pitfall | User Impact | Better Approach |
|---------|-------------|-----------------|
| No distinction between "due today" and "overdue" in the daily plan | User cannot triage quickly — everything looks equally urgent | Separate sections: Overdue (red tint), Due Today (normal), Upcoming (muted). PROJECT.md already specifies this — do not flatten it during implementation. |
| Marking a task complete immediately removes it from today's view | User cannot see what they've done today — feels like the work disappeared | Show completed tasks in a "Done today" collapsed section or with a strikethrough for the session |
| Cleanliness indicator updating in real time during rapid completions | Flickering percentage feels wrong | Debounce indicator updates or recalculate only on screen focus, not per-completion |
| Scheduling next due date from the moment the completion button is tapped | A task completed late at 11 PM schedules the next occurrence for 11 PM in N days — misaligned with calendar days | Compute next due date from the calendar date of completion, not the timestamp |
| Notification fires while the user has the app open | Duplicate information; notification feels spammy | Cancel or suppress the daily summary notification if the app is in the foreground at scheduled time |
| German-only strings hardcoded as literals scattered throughout widget code | Impossible to add English localization later without full-codebase surgery | Use Flutter's `l10n` infrastructure (`AppLocalizations`) from the start, even with only one locale — adding a second locale later is then trivial |
---
## "Looks Done But Isn't" Checklist
Things that appear complete but are missing critical pieces.
- [ ] **Notifications:** Tested on a real Android 13+ device (not emulator) with a fresh app install — permission prompt appears, notification fires at scheduled time.
- [ ] **Notifications after reboot:** Device rebooted after scheduling a notification — notification still fires at the correct time.
- [ ] **Drift migrations:** App installed over an older APK (not fresh install) — data survives, no crash, no missing columns.
- [ ] **Due date calculation:** Unit tests cover completion at 11:58 PM, 12:02 AM, and on the last day of a short month (e.g., Feb 28/29).
- [ ] **Overdue logic:** Tasks not completed for multiple intervals show as overdue for the correct number of days, not just "1 day overdue."
- [ ] **Cleanliness indicator:** Rooms with no tasks do not divide by zero.
- [ ] **Task deletion:** Deleting a task cancels its scheduled notification — no orphaned alarms in the AlarmManager queue.
- [ ] **Localization:** All user-visible strings come from `AppLocalizations`, not hardcoded German literals.
- [ ] **Dark mode:** Every screen renders correctly in both light and dark theme — test on device, not just the Flutter preview.
- [ ] **Task with no recurrence interval:** One-time tasks (if supported) do not trigger infinite "overdue" state after completion.
---
## Recovery Strategies
When pitfalls occur despite prevention, how to recover.
| Pitfall | Recovery Cost | Recovery Steps |
|---------|---------------|----------------|
| Shipped without migration; users lost data | HIGH | Ship a hotfix that detects the broken schema version, performs a safe re-creation with sane defaults, and notifies the user their task history was reset |
| Due dates drifted due to timestamp vs. date bug | MEDIUM | Write a one-time migration that normalizes all stored due dates to date-only (midnight local time); bump schemaVersion |
| Notification permission never requested; users have no notifications | LOW | Add permission request on next app launch; show an in-app banner explaining why |
| Hardcoded German strings throughout widgets | HIGH | Requires systematic extraction to ARB files; no shortcut — this is a full codebase refactor |
| `autoDispose` caused state loss and data reload on every navigation | MEDIUM | Add `keepAlive: true` to affected providers; test all navigation flows after the change |
| Notification icon stripped by R8 | LOW | Add `keep.xml` proguard rule; rebuild and redeploy |
---
## Pitfall-to-Phase Mapping
How roadmap phases should address these pitfalls.
| Pitfall | Prevention Phase | Verification |
|---------|------------------|--------------|
| Drift schema without migration workflow | Foundation (DB setup) | Run `drift_dev schema verify` passes; migration test file exists |
| Due date timestamp vs. date bug | Core scheduling logic | Unit tests for midnight, month-end, and interval edge cases all pass |
| Android notification runtime permissions | Notifications phase | Fresh install on API 33+ physical device shows permission prompt; notification fires |
| Notification lost after reboot | Notifications phase | Device rebooted; notification rescheduled and fires at correct time |
| `ref.watch` outside `build` | Foundation (project setup) | `riverpod_lint` enabled; no lint warnings in initial project scaffold |
| `autoDispose` state loss on navigation | Core UI phase | Navigate away and back 3 times; no loading flash, no database re-query |
| Hardcoded German strings | Foundation (project setup) | `l10n.yaml` and `AppLocalizations` set up before first UI widget is written |
| Notification icon stripped by R8 | Notifications phase | Release build tested (not just debug); notification icon renders correctly |
| Overdue logic / cleanliness indicator divide-by-zero | Daily plan view phase | Empty room (0 tasks) renders without crash; indicator shows neutral state |
| Room photo stored as blob | Room management phase | Photos stored as file paths; database size stays small after adding 5 rooms with photos |
---
## Sources
- [Drift official migration docs](https://drift.simonbinder.eu/migrations/) — schema versioning and `stepByStep` API
- [Riverpod official docs — Refs](https://riverpod.dev/docs/concepts2/refs) — `ref.watch` vs `ref.read` vs `ref.listen` semantics
- [Riverpod official docs — Auto Dispose](https://riverpod.dev/docs/concepts2/auto_dispose) — `autoDispose` lifecycle
- [flutter_local_notifications pub.dev](https://pub.dev/packages/flutter_local_notifications) — Android permission requirements and migration notes
- [Android Developer docs — Notification permission](https://developer.android.com/develop/ui/views/notifications/notification-permission) — `POST_NOTIFICATIONS` runtime permission
- [Android Developer docs — Schedule exact alarms (API 14)](https://developer.android.com/about/versions/14/changes/schedule-exact-alarms) — `SCHEDULE_EXACT_ALARM` denied by default on API 33+
- [Andrea Bizzotto — Riverpod data caching and provider lifecycle](https://codewithandrea.com/articles/flutter-riverpod-data-caching-providers-lifecycle/) — `keepAlive`, `autoDispose` patterns
- [Flutter Material 3 migration guide](https://docs.flutter.dev/release/breaking-changes/material-3-migration) — widget replacements and theming changes
- [Common SQLite mistakes — Sparkleo/Medium](https://medium.com/@sparkleo/common-sqlite-mistakes-flutter-devs-make-and-how-to-avoid-them-1102ab0117d5) — N+1 queries, transactions, normalization
- [ha-chore-helper GitHub](https://github.com/bmcclure/ha-chore-helper) — "every" vs "after" scheduling modes in chore apps, midnight edge cases
- [Drift Local Database Part 1 — Medium](https://r1n1os.medium.com/drift-local-database-for-flutter-part-1-intro-setup-and-migration-09a64d44f6df) — `schemaVersion` and migration workflow for new developers
---
*Pitfalls research for: Local-first Flutter household chore management app (HouseHoldKeaper)*
*Researched: 2026-03-15*

206
.planning/research/STACK.md Normal file
View File

@@ -0,0 +1,206 @@
# Stack Research
**Domain:** Local-first Flutter household chore management app (Android-first)
**Researched:** 2026-03-15
**Confidence:** HIGH (versions verified directly from pub.dev; compatibility notes cross-checked against GitHub issues and official docs)
---
## Recommended Stack
### Core Technologies
| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| Flutter SDK | ^3.41.x (stable) | UI framework and build toolchain | Current stable. Riverpod 3.3.x requires Dart 3.7+; Flutter 3.41 ships Dart 3.7. Earlier stable releases (pre-3.41) had a transitive `analyzer` conflict with Riverpod 3.2+ that was fixed in 3.41.1. |
| Dart SDK | ^3.7.0 | Language runtime | Minimum required by `flutter_riverpod ^3.3.0`. Set as lower bound in `pubspec.yaml`. |
| flutter_riverpod | ^3.3.1 | State management and dependency injection | The project already decided on Riverpod. v3.x is current stable (released Sep 2025); it unifies `AutoDispose`/`Family` variants, simplifies `Ref` (no more subclasses), and adds built-in offline persistence (opt-in). Start on v3 — migrating from v2 later is painful. |
| riverpod_annotation | ^4.0.2 | Annotations for code-generation-based providers | Enables `@riverpod` annotation. Required companion to `riverpod_generator`. Code-gen is the recommended path in Riverpod 3 — less boilerplate, compile-safe, `autoDispose` by default. |
| drift | ^2.32.0 | Type-safe reactive SQLite ORM | The project already decided on Drift. Reactive streams (`.watch()`) integrate naturally with `StreamProvider` in Riverpod. Type-safe query generation catches errors at compile time. Built-in migration support is essential for a long-lived local-first app. |
| drift_flutter | ^0.3.0 | Flutter-specific Drift setup helper | Bundles `sqlite3_flutter_libs`, handles `getApplicationDocumentsDirectory()` automatically. Eliminates manual SQLite platform setup on Android. Use `driftDatabase(name: 'household_keeper')` to open the DB. |
### Supporting Libraries
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| freezed | ^3.2.5 | Immutable value objects with copyWith, equality, pattern matching | Use for all domain entities (Room, Task, TaskCompletion) and Riverpod state classes. `@freezed` + code-gen eliminates hand-written `==` / `copyWith`. |
| freezed_annotation | ^3.1.0 | Annotations for freezed code generation | Required companion to `freezed`. Always add to `dependencies` (not `dev_dependencies`). |
| go_router | ^17.1.0 | Declarative URL-based navigation | Official Flutter team package. The app has shallow navigation (RoomList → TaskList → DailyPlan), but GoRouter's `ShellRoute` cleanly handles a bottom nav bar with persistent state per tab. Simple enough for this app's needs; widely supported. |
| flutter_local_notifications | ^21.0.0 | Scheduled local notifications | For the daily summary notification (required by PROJECT.md). No Firebase needed — purely on-device scheduling via Android's AlarmManager. Requires Android 7.0+ (API 24). |
| timezone | ^0.11.0 | Timezone-aware scheduled notifications | Required by `flutter_local_notifications` for reliable scheduled notification timing across daylight-saving boundaries. |
| flex_color_scheme | ^8.4.0 | Material 3 theme generation | Generates a fully consistent M3 `ThemeData` — including legacy color properties that `ColorScheme.fromSeed()` misses — from a single seed color. The calm muted-green palette specified in PROJECT.md is straightforward to express as a seed color. Reduces ~300 lines of manual theme code to ~20. |
| intl | ^0.19.0 | Date and number formatting; localization infrastructure | Format due dates (German locale: `dd.MM.yyyy`), relative strings ("übermorgen", "heute"). Also the underlying engine for `flutter_localizations`. Even for a German-only MVP, you still need date formatting. |
### Development Tools and Code-Generation Packages
| Tool / Package | Version | Purpose | Notes |
|----------------|---------|---------|-------|
| riverpod_generator | ^4.0.3 (dev) | Generates provider boilerplate from `@riverpod` annotations | Run `dart run build_runner watch -d` during development. The `-d` flag deletes conflicting outputs before building. |
| drift_dev | ^2.32.0 (dev) | Generates type-safe Drift query code from table definitions | Same `build_runner` run handles both Drift and Riverpod generation. |
| build_runner | ^2.12.2 (dev) | Orchestrates all code generation | One `build_runner watch` invocation covers all generators in the project. |
| freezed (dev) | ^3.2.5 (dev) | Generates immutable class implementations | Note: `freezed` appears in both `dependencies` (as annotation) and `dev_dependencies` (as generator). `freezed_annotation` goes in `dependencies`, `freezed` itself in `dev_dependencies`. |
| flutter_lints | ^6.0.0 (dev) | Official Flutter lint ruleset | Default in new Flutter projects. Catches common errors and style issues. Extend with stricter rules in `analysis_options.yaml` as the codebase grows. |
| riverpod_lint | ^4.0.x (dev) | Riverpod-specific lint rules | Catches incorrect provider usage: unused providers, missing `ref.watch` inside builds, incorrect `async` patterns. Add alongside `flutter_lints`. Check exact version on pub.dev at setup time — tracks `riverpod_generator` versions. |
---
## pubspec.yaml
```yaml
name: household_keeper
description: Local-first chore management app for Android.
version: 1.0.0+1
environment:
sdk: ^3.7.0
flutter: ">=3.41.0"
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# State management
flutter_riverpod: ^3.3.1
riverpod_annotation: ^4.0.2
# Database
drift: ^2.32.0
drift_flutter: ^0.3.0
# Immutable models
freezed_annotation: ^3.1.0
# Navigation
go_router: ^17.1.0
# Notifications
flutter_local_notifications: ^21.0.0
timezone: ^0.11.0
# Theming
flex_color_scheme: ^8.4.0
# Localization / date formatting
intl: ^0.19.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
# Code generation
riverpod_generator: ^4.0.3
riverpod_lint: ^4.0.0 # verify exact version on pub.dev at setup time
build_runner: ^2.12.2
drift_dev: ^2.32.0
freezed: ^3.2.5
flutter:
generate: true # required for flutter_localizations ARB code gen
```
---
## Alternatives Considered
| Category | Recommended | Alternative | Why Not Alternative |
|----------|-------------|-------------|---------------------|
| State management | Riverpod 3.x | flutter_bloc | Project already decided Riverpod. Bloc requires more boilerplate (events, states, blocs) for what this app needs. Riverpod integrates more naturally with Drift's reactive streams. |
| State management | Riverpod 3.x | Riverpod 2.x | v2 is no longer maintained upstream. v3 was released Sep 2025; migrating later is a breaking-change effort. Start on v3 now. |
| Database | Drift | Isar | Isar is a NoSQL document store — less natural for the relational structure (rooms → tasks → completions). Drift's reactive streams fit Riverpod's `StreamProvider` perfectly. Isar's future is uncertain after its maintainer handed it off. |
| Database | Drift | ObjectBox | ObjectBox is a NoSQL object store. Same relational argument applies. Drift's SQL gives more flexibility for complex queries (e.g., "overdue tasks per room" with joins). ObjectBox has a commercial tier for some features. |
| Database | Drift | sqflite + raw SQL | Raw sqflite requires manual query strings, manual mapping, no reactive streams, no compile-time safety. The project explicitly chose Drift over raw sqflite for type safety and migration support. |
| Navigation | go_router | auto_route | auto_route has slightly better type safety but requires more setup. go_router is an official Flutter package — simpler, well-documented, maintained by the Flutter team. Good enough for this app's flat navigation graph. |
| Navigation | go_router | Navigator 2.0 (raw) | Excessive boilerplate for a 4-screen app. go_router wraps Navigator 2.0 cleanly. |
| Theming | flex_color_scheme | Manual ThemeData | Manual M3 theming misses legacy color sync (e.g., `ThemeData.cardColor`, `dialogBackgroundColor` still default to wrong values without explicit override). flex_color_scheme fills all gaps. For a calm, consistent design, the investment of one dependency is worth it. |
| Theming | flex_color_scheme | dynamic_color (Material You) | Dynamic color adapts to wallpaper — inappropriate for a fixed calm-palette design. The app defines its own identity; it should not shift colors based on user wallpaper. |
| Notifications | flutter_local_notifications | awesome_notifications | awesome_notifications is more powerful but significantly heavier. flutter_local_notifications is sufficient for a single daily scheduled notification. |
| Localization | flutter_localizations + intl | easy_localization | For German-only MVP, flutter_localizations with ARB files is the official path. easy_localization adds another dependency with no benefit until multi-language is needed. When English is added in v1.1, the ARB infrastructure is already in place. |
---
## What NOT to Use
| Avoid | Why | Use Instead |
|-------|-----|-------------|
| Provider (package) | Predecessor to Riverpod, now in maintenance mode. Much weaker compile-time safety. Migration from Provider to Riverpod later is non-trivial. | `flutter_riverpod ^3.3.1` |
| StateNotifierProvider / StateProvider (Riverpod legacy) | Moved to `package:flutter_riverpod/legacy.dart` in Riverpod 3.0. The official docs now use `@riverpod` + `AsyncNotifier` / `Notifier`. Using legacy providers means you will need to migrate soon after starting. | `@riverpod` annotated `AsyncNotifier` / `Notifier` classes |
| Hive | No first-class SQLite support. Key-value store with no query language — cannot express "tasks due today across all rooms" without loading the entire dataset. Hive v2 has had stale maintenance; Isar (its successor) is now also uncertain. | Drift |
| Firebase (any service) | PROJECT.md is explicit: zero backend, zero network dependencies, no analytics, no tracking. Firebase Firestore, Firebase Analytics, Firebase Crashlytics, Firebase Auth — all out of scope. | Nothing; handle everything locally. |
| GetIt (service locator) | Riverpod already handles DI through providers. Using GetIt alongside Riverpod creates two competing DI systems and makes provider scoping/testing harder. | Riverpod providers as the single DI mechanism |
| MobX | Not compatible with Riverpod. Requires its own code generation and observable pattern. Complexity without benefit when Riverpod 3.x already handles reactive state. | `flutter_riverpod + riverpod_generator` |
| flutter_bloc alongside Riverpod | Two competing state systems in one codebase creates cognitive overhead and testing complexity. | Pick one: Riverpod for this project. |
| `StateNotifier` (standalone package) | Deprecated upstream. Riverpod 3.x `Notifier` replaces it. | `Notifier<T>` with `@riverpod` |
---
## Stack Patterns by Variant
**For reactive Drift queries (read-only list screens):**
- Use `@riverpod Stream<List<T>>` backed by a DAO `.watch()` method
- The screen consumes via `ref.watch(roomsProvider)` returning `AsyncValue<List<Room>>`
- Drift emits a new list automatically when the database changes — no manual invalidation
**For mutations (completing a task, adding a room):**
- Use `@riverpod class TaskNotifier extends AsyncNotifier<void>` with explicit methods
- Call `ref.invalidate(tasksProvider)` after mutation to trigger rebuild on affected watchers
- In Riverpod 3.x, the new experimental `Mutation` API is available but still experimental — use manual invalidation for stability
**For the daily plan (complex cross-room query):**
- Implement a Drift query that joins tasks + rooms filtered by due date
- Expose via `@riverpod Stream<DailyPlanSummary>` so the plan view auto-updates as tasks are completed
- Do NOT compute the daily plan in the UI layer — push join logic into a DAO method
**For notification scheduling:**
- Schedule the daily notification at app startup (in a `ProviderScope` override or `app.dart` `initState`)
- Re-schedule when the user completes all tasks for the day or explicitly changes notification time
- Notification scheduling is one-time daily, not per-task — keeps it simple for MVP
**For German-only MVP localization:**
- Use hardcoded German strings in widgets for MVP rather than ARB files
- Set up the ARB infrastructure skeleton (`l10n.yaml`, `lib/l10n/app_de.arb`) before v1.1 so adding English is a string extraction exercise, not an architectural change
- Do NOT use `String.fromCharCodes` or runtime locale detection — always target `de`
---
## Version Compatibility Notes
| Package | Compatible With | Notes |
|---------|-----------------|-------|
| flutter_riverpod ^3.3.1 | Flutter >=3.41.0, Dart >=3.7 | Pre-3.41 Flutter stable had a transitive `analyzer` conflict. Resolved in Flutter 3.41.1. Use current stable (3.41.2). |
| drift ^2.32.0 + drift_dev ^2.32.0 | Build-runner ^2.12.2 | drift and drift_dev versions must match exactly. |
| riverpod_generator ^4.0.3 | riverpod_annotation ^4.0.2 | Generator and annotation major versions must match (both 4.x). |
| freezed ^3.2.5 | freezed_annotation ^3.1.0 | Major versions must match (both 3.x). |
| flutter_local_notifications ^21.0.0 | Android API 24+ (Android 7.0+) | Minimum API level 24. Requires `RECEIVE_BOOT_COMPLETED` permission in `AndroidManifest.xml` and core library desugaring in `build.gradle.kts`. |
| flex_color_scheme ^8.4.0 | Flutter >=3.x, Dart >=3.x | M3 enabled by default in v8+. No additional configuration needed. |
---
## Sources
- [pub.dev/packages/flutter_riverpod](https://pub.dev/packages/flutter_riverpod) — version 3.3.1 verified directly
- [pub.dev/packages/riverpod_generator](https://pub.dev/packages/riverpod_generator) — version 4.0.3 verified directly
- [pub.dev/packages/riverpod_annotation](https://pub.dev/packages/riverpod_annotation) — version 4.0.2 verified directly
- [riverpod.dev/docs/whats_new](https://riverpod.dev/docs/whats_new) — Riverpod 3.0 feature list
- [riverpod.dev/docs/3.0_migration](https://riverpod.dev/docs/3.0_migration) — breaking changes guide
- [pub.dev/packages/drift](https://pub.dev/packages/drift) — version 2.32.0 verified directly
- [pub.dev/packages/drift_flutter](https://pub.dev/packages/drift_flutter) — version 0.3.0 verified directly
- [drift.simonbinder.eu/setup](https://drift.simonbinder.eu/setup/) — official Drift setup guide
- [pub.dev/packages/go_router](https://pub.dev/packages/go_router) — version 17.1.0 verified directly
- [pub.dev/packages/flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) — version 21.0.0 verified directly
- [pub.dev/packages/freezed](https://pub.dev/packages/freezed) — version 3.2.5 verified directly
- [pub.dev/packages/freezed_annotation](https://pub.dev/packages/freezed_annotation) — version 3.1.0 verified directly
- [pub.dev/packages/flex_color_scheme](https://pub.dev/packages/flex_color_scheme) — version 8.4.0 verified directly
- [pub.dev/packages/timezone](https://pub.dev/packages/timezone) — version 0.11.0 verified directly
- [pub.dev/packages/build_runner](https://pub.dev/packages/build_runner) — version 2.12.2 verified directly
- [pub.dev/packages/flutter_lints](https://pub.dev/packages/flutter_lints) — version 6.0.0 verified directly
- [github.com/rrousselGit/riverpod/issues/4676](https://github.com/rrousselGit/riverpod/issues/4676) — Riverpod 3.2+/stable channel compatibility; resolved in Flutter 3.41.1 (MEDIUM confidence)
- [docs.flutter.dev/release/archive](https://docs.flutter.dev/release/archive) — Flutter 3.41 current stable confirmed
- [docs.flexcolorscheme.com](https://docs.flexcolorscheme.com/) — M3 legacy color sync coverage (MEDIUM confidence, official docs)
---
*Stack research for: Local-first Flutter household chore management app (HouseHoldKeaper)*
*Researched: 2026-03-15*

View File

@@ -0,0 +1,207 @@
# Project Research Summary
**Project:** HouseHoldKeaper
**Domain:** Local-first Flutter household chore management app (Android-first)
**Researched:** 2026-03-15
**Confidence:** HIGH
## Executive Summary
HouseHoldKeaper occupies a genuine market gap: every competing chore app (Tody, Sweepy, BeTidy, OurHome) requires either a subscription, cloud sync, or an account to be useful. None are fully private, fully free, and room-organized simultaneously. The recommended approach is a clean-architecture Flutter app using Riverpod 3 for state management and Drift for reactive SQLite — a combination that is now well-documented, has strong community templates, and maps cleanly to the feature requirements. The "local-first" constraint is not a limitation to work around; it is the product's core differentiator and shapes every architectural decision in a positive direction.
The recommended build order follows strict feature dependency: rooms must exist before tasks, tasks before auto-scheduling, auto-scheduling before the daily plan view. This dependency chain also defines the phase structure — each phase produces a working increment that can be evaluated. The stack chosen (Flutter 3.41, Riverpod 3.3, Drift 2.32, Freezed 3.2) is current-stable with verified pub.dev versions; all major version combinations are explicitly compatible. The code-generation layer (build_runner, riverpod_generator, drift_dev, freezed) means initial setup is non-trivial but the reward is compile-safe, type-safe, low-boilerplate code throughout.
The highest-risk area is not implementation complexity but correctness in two specific subsystems: due-date scheduling (timezone/date arithmetic edge cases) and Android notification permissions (API 33+ runtime permission requirements). Both are well-understood problems with documented solutions, but both fail silently in development and only surface on real devices. Addressing these with unit tests and physical device verification before marking them "done" eliminates the most likely post-ship bugs.
## Key Findings
### Recommended Stack
The stack is fully determined by prior project decisions (Riverpod, Drift) plus version research that confirms the correct current-stable versions. The critical compatibility constraint is Flutter 3.41+ (Dart 3.7+) for Riverpod 3.3 — pre-3.41 Flutter has a transitive analyzer conflict that was fixed in 3.41.1. All library pairs with major version coupling (drift/drift_dev, riverpod_generator/riverpod_annotation, freezed/freezed_annotation) have been verified to match.
See `.planning/research/STACK.md` for the complete `pubspec.yaml` and alternatives considered.
**Core technologies:**
- Flutter 3.41 / Dart 3.7: UI framework — minimum required by Riverpod 3.3; current stable
- flutter_riverpod 3.3.1: State management and DI — `@riverpod` code-gen is the correct path; start on v3, migrating from v2 is painful
- drift 2.32 + drift_flutter 0.3: Reactive SQLite ORM — `.watch()` streams integrate naturally with Riverpod `StreamProvider`; type-safe compile-time queries
- freezed 3.2.5: Immutable domain entities — eliminates hand-written `==`/`copyWith`; required for all domain objects
- go_router 17.1: Navigation — official Flutter team package; `ShellRoute` handles bottom nav with persistent tab state
- flutter_local_notifications 21.0: On-device scheduled notifications — no Firebase needed; requires Android API 24+
- flex_color_scheme 8.4: Material 3 theme generation — fills legacy color sync gaps that `ColorScheme.fromSeed()` misses
**What not to use:** Firebase (any service), Provider package, Riverpod legacy providers (StateNotifierProvider/StateProvider), Hive, GetIt, MobX, flutter_bloc alongside Riverpod.
### Expected Features
The feature research identified a clear three-tier MVP definition. The core scheduling loop (room → task → auto-schedule → daily plan → mark done → reschedule) must be proven before adding any differentiators. The cleanliness indicator is a P1 because it is derived from existing data with no additional database work — it costs almost nothing to include at MVP.
See `.planning/research/FEATURES.md` for the full competitor analysis and feature prioritization matrix.
**Must have (table stakes):**
- Room CRUD with icons — primary organizational unit; every competitor uses rooms
- Task CRUD with recurrence intervals (daily/every N days/weekly/monthly/seasonal) — core scheduling primitive
- Auto-scheduling next due date on completion — the "fire and forget" reliability promise
- Daily plan view with three-band layout (Overdue / Due Today / Upcoming) — primary answer to "what do I do now?"
- Task completion action — the satisfying feedback loop closure
- Bundled task templates per room type (German) — eliminates blank-slate setup anxiety
- Daily summary notification — single notification sufficient; per-task reminders are scope creep
- Light/dark theme (system-default follow) — Material 3 baseline expectation
- Cleanliness indicator per room — derived from overdue task ratio; zero marginal database complexity
- Task sorting by due date — minimum list usability
**Should have (competitive):**
- Task history / completion log — couples accountability use case; Drift makes storage trivial once completions are being recorded
- Vacation / pause mode — first travel use case will surface this immediately
- Data export/import (JSON) — specified in PROJECT.md for v1.1
- Task effort/duration tagging — enables time-based filtering ("I have 20 minutes")
- One-time project task type — "Paint the hallway" use case; single model flag, minimal complexity
**Defer (v2+):**
- Statistics and insights dashboard — high UI complexity; requires historical data volume to be meaningful
- Self-hosted sync (CouchDB/SQLite replication) — the principled multi-device answer; complex
- Tablet-optimized layout — secondary form factor
- Onboarding wizard — overkill for initial personal use
- Custom accent color picker — non-core personalization
**Anti-features to reject:** Cloud sync, family profiles/user accounts, gamification, in-app purchases, AI task suggestions, per-task push notifications.
### Architecture Approach
The recommended architecture is Clean Architecture with a feature-first folder structure: `core/` for the shared `AppDatabase` singleton and `NotificationService`, and `features/rooms/`, `features/tasks/`, `features/daily_plan/`, `features/completions/`, `features/templates/` each with their own `data/domain/presentation` split. This mirrors the standard Flutter/Riverpod community pattern and makes the build order self-evident — domain defines contracts, data implements them, presentation consumes them.
See `.planning/research/ARCHITECTURE.md` for the full system diagram, data flow diagrams, and anti-patterns.
**Major components:**
1. AppDatabase (Drift singleton) — root database; all DAOs are getters on this single instance; registered via one Riverpod provider at app root
2. Feature DAOs (RoomDao, TaskDao, CompletionDao) — type-safe SQL wrappers; thin, no business logic; expose reactive `.watch()` streams
3. Repository interfaces + implementations — domain defines abstract contracts; data layer implements using DAOs; presentation never imports from `data/` directly
4. Riverpod StreamProviders — expose Drift `.watch()` streams to UI; use `keepAlive: true` for long-lived app-wide state (room list, task list, daily plan)
5. Riverpod AsyncNotifiers — handle all mutations (create/complete/delete); call repository, let Drift streams propagate changes automatically
6. SchedulingService (domain) — all recurrence math lives here, not in DAOs; pure Dart, fully testable
7. NotificationService — wraps `flutter_local_notifications`; fire-and-forget side effect; called from task completion notifier after writes
**Key data flow pattern:** Drift `.watch()` → StreamProvider → ConsumerWidget. Mutations go through AsyncNotifier → Repository → DAO → SQLite, then Drift automatically emits updated streams to all watchers. No manual invalidation needed for reads.
### Critical Pitfalls
Full prevention strategies, warning signs, and recovery steps in `.planning/research/PITFALLS.md`.
1. **Drift schema changes without migration workflow** — Establish `drift_dev make-migrations` + `schemaVersion` bump as a required step for every table change before writing the first table definition. Failures are silent in development and catastrophic on user updates.
2. **Due dates stored as DateTime (with time) instead of Date (calendar day)** — Store all due dates as date-only from day one. Midnight completions and timezone drift create subtle overdue logic bugs. Policy: rolling schedule, last-completion calendar date + interval.
3. **Android notification runtime permissions on API 33+**`POST_NOTIFICATIONS` and `SCHEDULE_EXACT_ALARM` must be requested at runtime, not just declared in the manifest. Test on a physical API 33+ device with a fresh install. Call `tz.initializeTimeZones()` at startup.
4. **Riverpod `ref.watch` / `ref.listen` outside `build()`** — Enable `riverpod_lint` before writing any feature code; it catches these at analysis time. Rule: `ref.watch` only in `build()`, `ref.read` in callbacks.
5. **`autoDispose` providers losing state on navigation** — Default `@riverpod` enables autoDispose. Use `@Riverpod(keepAlive: true)` for room list, task list, and daily plan providers to avoid loading flicker on every navigation.
## Implications for Roadmap
Feature dependencies discovered in research dictate a clear phase order: rooms before tasks before scheduling before daily plan before notifications. Architecture research confirms a bottom-up build order (database schema → DAOs → domain → repositories → providers → UI). Pitfall research identifies which phases need protective setup work before feature code begins. These constraints combine into a natural 6-phase structure.
### Phase 1: Foundation and Project Setup
**Rationale:** All subsequent phases depend on the database schema, Riverpod singleton patterns, linting rules, and localization infrastructure being correct from the start. Technical debt incurred here (hardcoded strings, missing migration workflow, wrong provider lifetimes) has the highest recovery cost of any phase.
**Delivers:** Compilable project scaffold with database opened, `riverpod_lint` enforcing correct ref usage, `l10n.yaml` + `AppLocalizations` skeleton in place (even if only German, one locale), `drift_dev make-migrations` workflow established, Drift `schemaVersion` = 1 with empty tables, and Material 3 theme via `flex_color_scheme`.
**Addresses:** Light/dark theme (system-default)
**Avoids:** Hardcoded German strings (recovery: full codebase refactor), `ref.watch` outside `build` (recovery: hard to find without linting), Drift migration gaps (recovery: data loss on upgrade), multiple AppDatabase instances (recovery: runtime crash)
### Phase 2: Room Management
**Rationale:** Rooms are the organizing container for everything else. No tasks, no templates, no cleanliness indicator can exist without rooms. This is also the simplest complete feature slice — ideal for proving the full Clean Architecture stack end-to-end (DAO → repository → provider → UI) before adding the complexity of scheduling.
**Delivers:** Room CRUD with icons, room list screen, room detail screen, `RoomDao` + `RoomRepository`, `StreamProvider<List<Room>>` watching all rooms, `AsyncNotifier` for mutations.
**Addresses:** Room CRUD with icons (table stakes MVP)
**Avoids:** Accessing AppDatabase directly in widgets (anti-pattern), storing room photos as blobs (store file paths only if photos are added)
### Phase 3: Task Management and Auto-Scheduling
**Rationale:** This is the largest and most complex phase — it implements the core scheduling primitive and the recurrence logic that everything else derives from. It must be solid before building the daily plan view (which purely consumes scheduled due dates) or the cleanliness indicator (which derives from overdue task counts). Due-date correctness unit tests must be written alongside, not after, the `SchedulingService`.
**Delivers:** Task CRUD with recurrence interval settings (daily/every N days/weekly/monthly/seasonal), `SchedulingService` (pure Dart, fully unit tested), auto-scheduling on task completion, `TaskDao` + `TaskRepository`, task list per room, task completion action (mark done + reschedule). Bundled task templates seeded on first launch via `AppDatabase.transaction()`.
**Addresses:** Task CRUD, auto-scheduling, task completion, bundled templates, task sorting by due date
**Avoids:** Due date timestamp vs. date bug (unit test midnight and month-end edge cases before wiring to UI), one-time tasks conflicting with auto-scheduler (add `isOneTime` flag to task model from the start — easier than migrating later)
### Phase 4: Daily Plan View and Cleanliness Indicator
**Rationale:** The daily plan view is the primary user-facing answer to "what do I do now?" It has no implementation complexity of its own — it is entirely a Drift query + rendering concern — but it requires rooms, tasks, and scheduling to be complete. The cleanliness indicator is derived from the same data and costs almost nothing to add alongside the daily plan. Both deliver the core value proposition in one phase.
**Delivers:** Daily plan screen with three-band layout (Overdue / Due Today / Upcoming), `DailyPlanService` (cross-room join query in Drift), `StreamProvider<DailyPlan>` with `keepAlive: true`, cleanliness indicator per room (overdue task ratio, computed in provider — not stored), "Done today" session visibility for completed tasks.
**Addresses:** Daily plan view, cleanliness indicator, task sorting
**Avoids:** Flattening overdue/today/upcoming into one list (per UX pitfalls), cleanliness indicator divide-by-zero on rooms with no tasks, computing daily plan in UI layer instead of DAO
### Phase 5: Notifications
**Rationale:** Notifications depend on the scheduling layer (needs due dates) and are self-contained from the UI perspective. They are separated into their own phase because they require special setup steps (manifest permissions, timezone init, boot receiver registration) and must be verified on a physical Android 13+ device — not just the emulator. Conflating this with Phase 3 would make that phase's scope and verification requirements too large.
**Delivers:** `NotificationService` wrapping `flutter_local_notifications`, daily summary notification scheduled at app startup, notification rescheduled after task completion, `tz.initializeTimeZones()` at startup, `RECEIVE_BOOT_COMPLETED` receiver for post-reboot rescheduling, runtime permission request at appropriate moment (first launch or settings toggle), notification suppressed when app is in foreground.
**Addresses:** Daily summary notification (table stakes MVP)
**Avoids:** Manifest-only permission declaration (runtime request required on API 33+), scheduling exact alarms without checking `canScheduleExactAlarms()`, missing timezone initialization (silent wrong-time bug), scheduling one alarm per future recurrence (hits Samsung 500-alarm limit; schedule next occurrence only)
### Phase 6: Polish and v1.x Features
**Rationale:** With the core loop proven (rooms → tasks → schedule → daily plan → mark done → notify), this phase adds the highest-value v1.x features before the first public release. Task history is particularly important because the completion event data has been accumulating since Phase 3 — it only requires a read surface, not new data collection. Vacation mode and data export round out the features listed in PROJECT.md for v1.1.
**Delivers:** Task history / completion log (scrollable per-task), vacation / pause mode (freeze due dates, resume on return), data export/import (JSON), additional sort options, any visual polish, dark mode verification on device.
**Addresses:** Task history, vacation mode, data export (all v1.x)
**Avoids:** Completed tasks disappearing with no trace (show "Done today" section or strikethrough), notification firing while app is in foreground
### Phase Ordering Rationale
- **Bottom-up dependency:** Every phase's output is a required input for the next phase. This is not an arbitrary choice — the feature dependency graph in FEATURES.md and the build order in ARCHITECTURE.md both point to this exact sequence.
- **Risk-front-loading:** The two highest-recovery-cost pitfalls (hardcoded strings and missing migration workflow) are addressed in Phase 1 before any feature code exists. The scheduling correctness pitfall is addressed in Phase 3 with unit tests before the UI depends on it. The notification pitfall is isolated to Phase 5 where it can be verified independently.
- **Deliverable increments:** Each phase produces something evaluable. After Phase 2, rooms work. After Phase 3, the full scheduling loop works. After Phase 4, the app is usable daily. After Phase 5, the app is notification-capable. After Phase 6, it is releasable.
- **Cleanliness indicator in Phase 4 (not Phase 3):** The indicator derives from overdue task count per room, which requires the scheduling layer to have computed due dates. Including it in Phase 4 alongside the daily plan is correct — both consume the same underlying data.
### Research Flags
Phases likely needing deeper research during planning:
- **Phase 5 (Notifications):** Android notification permission flows, exact alarm scheduling, and Doze mode behavior have version-specific variations that merit a focused research phase. The pitfalls research covers the known issues, but verifying against the current `flutter_local_notifications` 21.0 API during planning is worthwhile.
- **Phase 3 (Scheduling):** The recurrence policy edge cases (seasonal intervals, what "monthly" means for tasks scheduled on the 31st) are not fully specified and will need decision-making during planning. The domain is understood but the product decisions are not yet made.
Phases with standard patterns (skip research-phase):
- **Phase 1 (Foundation):** Flutter project setup, `flex_color_scheme` theming, and ARB localization infrastructure are all well-documented with official guides.
- **Phase 2 (Room Management):** Standard Drift DAO + Riverpod StreamProvider pattern; fully covered by CodeWithAndrea templates and official Drift docs.
- **Phase 4 (Daily Plan / Cleanliness):** The Drift cross-table query pattern and provider derivation are standard; no novel integration needed.
- **Phase 6 (Polish):** Task history and export are CRUD extensions of existing patterns; no new architectural territory.
## Confidence Assessment
| Area | Confidence | Notes |
|------|------------|-------|
| Stack | HIGH | All versions verified directly on pub.dev; compatibility constraints cross-checked against GitHub issues and official changelogs |
| Features | MEDIUM-HIGH | Competitor apps analyzed directly; user pain points from multiple review sources including MIT Technology Review; feature prioritization reflects documented user behavior |
| Architecture | HIGH | Cross-verified across official Flutter docs, CodeWithAndrea (authoritative Flutter architecture resource), official Drift docs, and multiple community templates using the same Riverpod + Drift combination |
| Pitfalls | HIGH (Riverpod/Drift specifics), MEDIUM (scheduling edge cases) | Riverpod and Drift pitfalls sourced from official docs and known issues; Android notification permission requirements from official Android developer docs; scheduling midnight edge cases from chore app open-source analysis |
**Overall confidence:** HIGH
### Gaps to Address
- **Recurrence policy details:** The app needs a documented decision on what "monthly" means for a task that was scheduled on the 31st, and what "seasonal" means in terms of interval days. These are product decisions, not technical ones — address in requirements definition.
- **Seasonal interval definition:** "Seasonal" appears in the feature list as a recurrence option but is not defined. Is it 90 days? 3 months? Calendar-season-based? Decide before implementing `SchedulingService`.
- **Icon set for rooms:** The feature research specifies rooms need icon support but does not specify the icon source. Flutter's Material Icons are the obvious choice; confirm whether custom icons are in scope before Phase 2.
- **First-launch template seeding UX:** The architecture specifies templates are seeded on first launch but does not specify whether this is silent (just seeds the data) or shown to the user (a "set up your rooms" prompt). Decide before Phase 2 planning.
- **Notification time configuration:** The daily summary notification needs a configurable time. Whether this is user-adjustable in settings or hardcoded (e.g., 8:00 AM) is not resolved. Decide before Phase 5 planning.
## Sources
### Primary (HIGH confidence)
- [pub.dev/packages/flutter_riverpod](https://pub.dev/packages/flutter_riverpod) — version 3.3.1 verified
- [pub.dev/packages/drift](https://pub.dev/packages/drift) — version 2.32.0 verified; migration and DAO patterns
- [riverpod.dev/docs/whats_new](https://riverpod.dev/docs/whats_new) — Riverpod 3.0 feature list and migration guide
- [drift.simonbinder.eu/setup](https://drift.simonbinder.eu/setup/) — official Drift setup; DAOs; migrations
- [docs.flutter.dev/app-architecture/design-patterns/sql](https://docs.flutter.dev/app-architecture/design-patterns/sql) — Flutter official SQL architecture pattern
- [docs.flutter.dev/app-architecture/design-patterns/offline-first](https://docs.flutter.dev/app-architecture/design-patterns/offline-first) — Flutter official offline-first pattern
- [developer.android.com — Notification permission](https://developer.android.com/develop/ui/views/notifications/notification-permission) — POST_NOTIFICATIONS runtime requirements
- [developer.android.com — Schedule exact alarms](https://developer.android.com/about/versions/14/changes/schedule-exact-alarms) — SCHEDULE_EXACT_ALARM behavior on API 33+
- [codewithandrea.com — Flutter App Architecture with Riverpod](https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/) — authoritative architecture reference
- [codewithandrea.com — Riverpod data caching and provider lifecycle](https://codewithandrea.com/articles/flutter-riverpod-data-caching-providers-lifecycle/) — keepAlive and autoDispose patterns
### Secondary (MEDIUM confidence)
- Tody, Sweepy, BeTidy, OurHome, Home Routines app analysis — competitor feature comparison
- [technologyreview.com/2022/05/10/1051954/chore-apps](https://www.technologyreview.com/2022/05/10/1051954/chore-apps/) — gamification long-term churn evidence
- [github.com/ssoad/flutter_riverpod_clean_architecture](https://github.com/ssoad/flutter_riverpod_clean_architecture) — community clean architecture template
- [dinkomarinac.dev — Building Local-First Flutter Apps with Riverpod, Drift, and PowerSync](https://dinkomarinac.dev/blog/building-local-first-flutter-apps-with-riverpod-drift-and-powersync/) — Riverpod + Drift integration patterns
- [github.com/rrousselGit/riverpod/issues/4676](https://github.com/rrousselGit/riverpod/issues/4676) — Riverpod 3.2+/stable channel compatibility; resolved in Flutter 3.41.1
### Tertiary (MEDIUM-LOW confidence)
- [ha-chore-helper GitHub](https://github.com/bmcclure/ha-chore-helper) — "every" vs "after" scheduling modes; midnight edge cases
- [docs.flexcolorscheme.com](https://docs.flexcolorscheme.com/) — M3 legacy color sync coverage (official docs but implementation details not independently verified)
---
*Research completed: 2026-03-15*
*Ready for roadmap: yes*