13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-daily-plan-and-cleanliness | 01 | execute | 1 |
|
true |
|
|
Purpose: Provides the reactive data foundation that the daily plan UI (Plan 02) will consume. Separated from UI to keep each plan at ~50% context. Output: DailyPlanDao with join query, DailyPlanState model, dailyPlanProvider, localization keys, and passing unit tests.
<execution_context> @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-daily-plan-and-cleanliness/3-CONTEXT.md @.planning/phases/03-daily-plan-and-cleanliness/03-RESEARCH.md@lib/core/database/database.dart @lib/features/tasks/data/tasks_dao.dart @lib/features/tasks/presentation/task_providers.dart @lib/core/providers/database_provider.dart @test/features/tasks/data/tasks_dao_test.dart
From lib/core/database/database.dart:
class Rooms extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text().withLength(min: 1, max: 100)();
TextColumn get iconName => text()();
IntColumn get sortOrder => integer().withDefault(const Constant(0))();
DateTimeColumn get createdAt => dateTime().clientDefault(() => DateTime.now())();
}
class Tasks extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get roomId => integer().references(Rooms, #id)();
TextColumn get name => text().withLength(min: 1, max: 200)();
TextColumn get description => text().nullable()();
IntColumn get intervalType => intEnum<IntervalType>()();
IntColumn get intervalDays => integer().withDefault(const Constant(1))();
IntColumn get anchorDay => integer().nullable()();
IntColumn get effortLevel => intEnum<EffortLevel>()();
DateTimeColumn get nextDueDate => dateTime()();
DateTimeColumn get createdAt => dateTime().clientDefault(() => DateTime.now())();
}
class TaskCompletions extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get taskId => integer().references(Tasks, #id)();
DateTimeColumn get completedAt => dateTime()();
}
@DriftDatabase(
tables: [Rooms, Tasks, TaskCompletions],
daos: [RoomsDao, TasksDao],
)
class AppDatabase extends _$AppDatabase { ... }
From lib/core/providers/database_provider.dart:
@Riverpod(keepAlive: true)
AppDatabase appDatabase(Ref ref) { ... }
From lib/features/tasks/presentation/task_providers.dart:
// Manual StreamProvider.family pattern (used because riverpod_generator
// has trouble with drift's generated Task type)
final tasksInRoomProvider =
StreamProvider.family.autoDispose<List<Task>, int>((ref, roomId) {
final db = ref.watch(appDatabaseProvider);
return db.tasksDao.watchTasksInRoom(roomId);
});
2. Create `lib/features/home/data/daily_plan_dao.dart` with:
- `@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])`
- `class DailyPlanDao extends DatabaseAccessor<AppDatabase> with _$DailyPlanDaoMixin`
- `Stream<List<TaskWithRoom>> watchAllTasksWithRoomName()`: innerJoin tasks with rooms on rooms.id.equalsExp(tasks.roomId), orderBy nextDueDate asc, map rows using readTable(tasks) and readTable(rooms)
- `Stream<int> watchCompletionsToday({DateTime? today})`: count TaskCompletions where completedAt >= startOfDay AND completedAt < endOfDay. Use customSelect with SQL COUNT(*) and readsFrom: {taskCompletions} for proper stream invalidation
3. Register DailyPlanDao in `lib/core/database/database.dart`:
- Add import for daily_plan_dao.dart
- Add `DailyPlanDao` to `@DriftDatabase(daos: [...])` list
- Run `dart run build_runner build --delete-conflicting-outputs` to regenerate database.g.dart and daily_plan_dao.g.dart
4. Create `test/features/home/data/daily_plan_dao_test.dart`:
- Follow existing tasks_dao_test.dart pattern: `AppDatabase(NativeDatabase.memory())`, setUp/tearDown
- Create 2 rooms and insert tasks with different due dates across them
- Test all behaviors listed above
- Use stream.first for single-emission testing (same pattern as existing tests)
IMPORTANT: The customSelect approach for watchCompletionsToday must use `readsFrom: {taskCompletions}` so Drift knows which table to watch for stream invalidation. Without this, the stream won't re-fire on new completions.
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/home/data/daily_plan_dao_test.dart
- DailyPlanDao registered in AppDatabase, code generation passes
- watchAllTasksWithRoomName returns tasks joined with room name, sorted by due date
- watchCompletionsToday returns accurate count of today's completions
- All unit tests pass
- TaskWithRoom and DailyPlanState models defined with correct fields
Task 2: Daily plan provider with date categorization, progress tracking, and localization keys
lib/features/home/presentation/daily_plan_providers.dart,
lib/l10n/app_de.arb
1. Create `lib/features/home/presentation/daily_plan_providers.dart` with:
- Import daily_plan_models.dart, database_provider.dart
- Define `dailyPlanProvider` as a manual `StreamProvider.autoDispose` (NOT using @riverpod, same pattern as tasksInRoomProvider because drift Task type causes riverpod_generator issues)
- Inside provider: `ref.watch(appDatabaseProvider)` to get db
- Watch `db.dailyPlanDao.watchAllTasksWithRoomName()` stream
- Use `.asyncMap()` on the task stream to:
a. Get completions today count via `db.dailyPlanDao.watchCompletionsToday().first`
b. Compute `today = DateTime(now.year, now.month, now.day)`, `tomorrow = today + 1 day`, `dayAfterTomorrow = tomorrow + 1 day`
c. Partition tasks into: overdue (dueDate < today), todayList (today <= dueDate < tomorrow), tomorrowList (tomorrow <= dueDate < dayAfterTomorrow)
d. Compute totalTodayCount = overdue.length + todayList.length + completedTodayCount
e. Return DailyPlanState with all fields
CRITICAL for progress accuracy: totalTodayCount includes completedTodayCount so the denominator stays stable as tasks are completed. Without this, completing a task would shrink the total (since the task moves to a future due date), making progress appear to go backward.
2. Add localization keys to `lib/l10n/app_de.arb` (add these AFTER existing keys, before the closing brace):
```json
"dailyPlanProgress": "{completed} von {total} erledigt",
"@dailyPlanProgress": {
"placeholders": {
"completed": { "type": "int" },
"total": { "type": "int" }
}
},
"dailyPlanSectionOverdue": "\u00dcberf\u00e4llig",
"dailyPlanSectionToday": "Heute",
"dailyPlanSectionUpcoming": "Demn\u00e4chst",
"dailyPlanUpcomingCount": "Demn\u00e4chst ({count})",
"@dailyPlanUpcomingCount": {
"placeholders": {
"count": { "type": "int" }
}
},
"dailyPlanAllClearTitle": "Alles erledigt! \ud83c\udf1f",
"dailyPlanAllClearMessage": "Keine Aufgaben f\u00fcr heute. Genie\u00dfe den Moment!",
"dailyPlanNoOverdue": "Keine \u00fcberf\u00e4lligen Aufgaben",
"dailyPlanNoTasks": "Noch keine Aufgaben angelegt"
```
Note: Use Unicode escapes for umlauts in ARB keys (same pattern as existing keys). The "all clear" title includes a star emoji per the established playful German tone from Phase 1.
3. Run `flutter gen-l10n` (or `flutter pub get` which triggers it) to regenerate localization classes.
4. Verify `dart analyze` passes clean on all new files.
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze lib/features/home/ lib/l10n/ && flutter test
- dailyPlanProvider defined as manual StreamProvider.autoDispose returning DailyPlanState
- Tasks correctly categorized into overdue (before today), today (today), tomorrow (next day)
- Progress total is stable: remaining overdue + remaining today + completedTodayCount
- All 10 new localization keys present in app_de.arb and code-generated without errors
- dart analyze clean, full test suite passes
- `flutter test test/features/home/data/daily_plan_dao_test.dart` -- all DAO tests pass
- `dart analyze lib/features/home/` -- no analysis errors
- `flutter test` -- full suite still passes (no regressions)
- DailyPlanDao registered in AppDatabase daos list
- dailyPlanProvider compiles and references DailyPlanDao correctly
<success_criteria>
- DailyPlanDao.watchAllTasksWithRoomName() returns reactive stream of tasks joined with room names
- DailyPlanDao.watchCompletionsToday() returns reactive count of today's completions
- dailyPlanProvider categorizes tasks into overdue/today/tomorrow with stable progress tracking
- All localization keys for daily plan UI are defined
- All existing tests still pass (no regressions from database.dart changes) </success_criteria>