Files
2026-03-16 20:12:01 +01:00

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
lib/features/home/data/daily_plan_dao.dart
lib/features/home/data/daily_plan_dao.g.dart
lib/features/home/domain/daily_plan_models.dart
lib/features/home/presentation/daily_plan_providers.dart
lib/core/database/database.dart
lib/core/database/database.g.dart
lib/l10n/app_de.arb
test/features/home/data/daily_plan_dao_test.dart
true
PLAN-01
PLAN-02
PLAN-03
PLAN-05
truths artifacts key_links
DailyPlanDao.watchAllTasksWithRoomName() returns tasks joined with room name, sorted by nextDueDate ascending
DailyPlanDao.watchCompletionsToday() returns count of completions recorded today
dailyPlanProvider categorizes tasks into overdue, today, and tomorrow sections
Progress total = remaining overdue + remaining today + completedTodayCount (stable denominator)
Localization keys for daily plan sections and progress text exist in app_de.arb
path provides exports
lib/features/home/data/daily_plan_dao.dart Cross-room join query and today's completion count
DailyPlanDao
TaskWithRoom
path provides exports
lib/features/home/domain/daily_plan_models.dart DailyPlanState data class for categorized daily plan data
DailyPlanState
path provides exports
lib/features/home/presentation/daily_plan_providers.dart Riverpod provider combining task stream and completion stream
dailyPlanProvider
path provides min_lines
test/features/home/data/daily_plan_dao_test.dart Unit tests for cross-room query, date categorization, completion count 80
from to via pattern
lib/features/home/data/daily_plan_dao.dart lib/core/database/database.dart @DriftAccessor registration DailyPlanDao
from to via pattern
lib/features/home/presentation/daily_plan_providers.dart lib/features/home/data/daily_plan_dao.dart db.dailyPlanDao.watchAllTasksWithRoomName() dailyPlanDao
Build the data and provider layers for the daily plan feature: a Drift DAO with cross-room join query, a DailyPlanState model with overdue/today/tomorrow categorization, a Riverpod provider combining task and completion streams, localization keys, and unit tests.

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);
});
Task 1: DailyPlanDao with cross-room join query and completion count lib/features/home/data/daily_plan_dao.dart, lib/features/home/data/daily_plan_dao.g.dart, lib/features/home/domain/daily_plan_models.dart, lib/core/database/database.dart, lib/core/database/database.g.dart, test/features/home/data/daily_plan_dao_test.dart - watchAllTasksWithRoomName returns empty list when no tasks exist - watchAllTasksWithRoomName returns tasks with correct room name from join - watchAllTasksWithRoomName returns tasks sorted by nextDueDate ascending - watchAllTasksWithRoomName returns tasks from multiple rooms with correct room name pairing - watchCompletionsToday returns 0 when no completions exist - watchCompletionsToday returns correct count of completions recorded today - watchCompletionsToday does not count completions from yesterday 1. Create `lib/features/home/domain/daily_plan_models.dart` with: - `TaskWithRoom` class: `final Task task`, `final String roomName`, `final int roomId`, const constructor - `DailyPlanState` class: `final List overdueTasks`, `final List todayTasks`, `final List tomorrowTasks`, `final int completedTodayCount`, `final int totalTodayCount`, const constructor
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>
After completion, create `.planning/phases/03-daily-plan-and-cleanliness/03-01-SUMMARY.md`