Files
HouseHoldKeaper/.planning/phases/05-calendar-strip/05-01-PLAN.md

12 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
05-calendar-strip 01 execute 1
lib/features/home/data/calendar_dao.dart
lib/features/home/data/calendar_dao.g.dart
lib/features/home/domain/calendar_models.dart
lib/features/home/presentation/calendar_providers.dart
lib/core/database/database.dart
lib/core/database/database.g.dart
lib/l10n/app_de.arb
lib/l10n/app_localizations_de.dart
lib/l10n/app_localizations.dart
test/features/home/data/calendar_dao_test.dart
true
CAL-02
CAL-05
truths artifacts key_links
Querying tasks for any arbitrary date returns exactly the tasks whose nextDueDate falls on that day
Querying overdue tasks for today returns all tasks whose nextDueDate is strictly before today
Querying a future date returns only tasks due that day, no overdue carry-over
CalendarState model holds selectedDate, overdue tasks, and day tasks as separate lists
Localization strings for calendar UI exist in ARB and generated files
path provides exports
lib/features/home/data/calendar_dao.dart Date-parameterized task queries
CalendarDao
path provides exports
lib/features/home/domain/calendar_models.dart CalendarState and reuse of TaskWithRoom
CalendarState
path provides exports
lib/features/home/presentation/calendar_providers.dart Riverpod provider for calendar state
calendarProvider
selectedDateProvider
path provides min_lines
test/features/home/data/calendar_dao_test.dart DAO unit tests 50
from to via pattern
lib/features/home/data/calendar_dao.dart lib/core/database/database.dart DAO registered in @DriftDatabase annotation CalendarDao
from to via pattern
lib/features/home/presentation/calendar_providers.dart lib/features/home/data/calendar_dao.dart Provider reads CalendarDao from AppDatabase db.calendarDao
Create the data layer, domain models, Riverpod providers, and localization strings for the calendar strip feature.

Purpose: The calendar strip UI (Plan 02) needs a data foundation that can answer "what tasks are due on date X?" and "what tasks are overdue relative to today?" without the old overdue/today/tomorrow bucketing. This plan builds that foundation and tests it.

Output: CalendarDao with date-parameterized queries, CalendarState model, Riverpod providers (selectedDateProvider + calendarProvider), new l10n strings, DAO 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/05-calendar-strip/5-CONTEXT.md

From lib/core/database/database.dart:

// Tables: Rooms, Tasks, TaskCompletions
// Existing DAOs: RoomsDao, TasksDao, DailyPlanDao
// CalendarDao must be added to the @DriftDatabase annotation daos list
// and imported at the top of database.dart

@DriftDatabase(
  tables: [Rooms, Tasks, TaskCompletions],
  daos: [RoomsDao, TasksDao, DailyPlanDao],  // ADD CalendarDao here
)
class AppDatabase extends _$AppDatabase { ... }

From lib/features/home/domain/daily_plan_models.dart:

class TaskWithRoom {
  final Task task;
  final String roomName;
  final int roomId;
  const TaskWithRoom({required this.task, required this.roomName, required this.roomId});
}

From lib/features/home/presentation/daily_plan_providers.dart:

// Pattern to follow: StreamProvider.autoDispose, manual (not @riverpod)
// because of drift's generated Task type
final dailyPlanProvider = StreamProvider.autoDispose<DailyPlanState>((ref) {
  final db = ref.watch(appDatabaseProvider);
  ...
});

From lib/features/home/data/daily_plan_dao.dart:

// Pattern: @DriftAccessor with tables, extends DatabaseAccessor<AppDatabase>
// Uses query.watch() for reactive streams
@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])
class DailyPlanDao extends DatabaseAccessor<AppDatabase> with _$DailyPlanDaoMixin { ... }

From lib/core/providers/database_provider.dart:

// appDatabaseProvider gives access to the database singleton
Task 1: Create CalendarDao with date-parameterized queries and tests lib/features/home/data/calendar_dao.dart, lib/core/database/database.dart, test/features/home/data/calendar_dao_test.dart - watchTasksForDate(date): returns tasks whose nextDueDate falls on the given calendar day (same year/month/day), joined with room name, sorted by task name alphabetically - watchOverdueTasks(referenceDate): returns tasks whose nextDueDate is strictly before referenceDate (start of day), joined with room name, sorted by nextDueDate ascending - watchTasksForDate for a date with no tasks returns empty list - watchOverdueTasks returns empty when no tasks are overdue - watchOverdueTasks does NOT include tasks due on the referenceDate itself - watchTasksForDate for a past date returns only tasks originally due that day (does NOT include overdue carry-over) 1. Create `lib/features/home/data/calendar_dao.dart`: - Class `CalendarDao` extends `DatabaseAccessor` with `_$CalendarDaoMixin` - Annotated `@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])` - `part 'calendar_dao.g.dart';` - Method `Stream> watchTasksForDate(DateTime date)`: Compute startOfDay and endOfDay (startOfDay + 1 day). Join tasks with rooms. Filter `tasks.nextDueDate >= startOfDay AND tasks.nextDueDate < endOfDay`. Order by `tasks.name` ascending. Map to `TaskWithRoom`. - Method `Stream> watchOverdueTasks(DateTime referenceDate)`: Compute startOfReferenceDay. Join tasks with rooms. Filter `tasks.nextDueDate < startOfReferenceDay`. Order by `tasks.nextDueDate` ascending. Map to `TaskWithRoom`. - Import `daily_plan_models.dart` for `TaskWithRoom` (reuse, don't duplicate).
2. Register CalendarDao in `lib/core/database/database.dart`:
   - Add import: `import '../../features/home/data/calendar_dao.dart';`
   - Add `CalendarDao` to the `daos:` list in `@DriftDatabase`

3. Run `dart run build_runner build --delete-conflicting-outputs` to generate `calendar_dao.g.dart` and updated `database.g.dart`.

4. Write `test/features/home/data/calendar_dao_test.dart` following the pattern in `test/features/home/data/daily_plan_dao_test.dart`:
   - Use in-memory database: `AppDatabase(NativeDatabase.memory())`
   - Create test rooms in setUp
   - Test group for watchTasksForDate:
     - Empty when no tasks
     - Returns only tasks due on the queried date (not before, not after)
     - Returns tasks from multiple rooms
     - Sorted alphabetically by name
   - Test group for watchOverdueTasks:
     - Empty when no overdue tasks
     - Returns tasks due before reference date
     - Does NOT include tasks due ON the reference date
     - Sorted by nextDueDate ascending
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/home/data/calendar_dao_test.dart CalendarDao registered in AppDatabase, both query methods return correct results for arbitrary dates, all DAO tests pass Task 2: Create CalendarState model, Riverpod providers, and localization strings lib/features/home/domain/calendar_models.dart, lib/features/home/presentation/calendar_providers.dart, lib/l10n/app_de.arb 1. Create `lib/features/home/domain/calendar_models.dart`: ```dart import 'package:household_keeper/features/home/domain/daily_plan_models.dart';
   /// State for the calendar day view: tasks for the selected date + overdue tasks.
   class CalendarDayState {
     final DateTime selectedDate;
     final List<TaskWithRoom> dayTasks;
     final List<TaskWithRoom> overdueTasks;

     const CalendarDayState({
       required this.selectedDate,
       required this.dayTasks,
       required this.overdueTasks,
     });

     /// True when viewing today and all tasks (day + overdue) have been completed
     /// (lists are empty but completions exist). Determined by the UI layer.
     bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty;
   }
   ```

2. Create `lib/features/home/presentation/calendar_providers.dart`:
   - Import Riverpod, database_provider, calendar_dao, calendar_models, daily_plan_models
   - `final selectedDateProvider = StateProvider<DateTime>((ref) { final now = DateTime.now(); return DateTime(now.year, now.month, now.day); });`
     This is NOT autoDispose -- the selected date persists as long as the app is alive (resets on restart naturally).
   - `final calendarDayProvider = StreamProvider.autoDispose<CalendarDayState>((ref) { ... });`
     Manual definition (not @riverpod) following dailyPlanProvider pattern.
     Reads `selectedDateProvider` to get the current date.
     Reads `appDatabaseProvider` to get the DB.
     Determines if selectedDate is today: `isToday = selectedDate == DateTime(now.year, now.month, now.day)`.
     Determines if selectedDate is in the future: `isFuture = selectedDate.isAfter(today)`.
     Watches `db.calendarDao.watchTasksForDate(selectedDate)`.
     For overdue: if `isToday`, also watch `db.calendarDao.watchOverdueTasks(selectedDate)`.
     If viewing a past date or future date, overdueTasks = empty.
     Per user decision: "When viewing past days: show what was due that day. When viewing future days: show only tasks due that day, no overdue carry-over."
     Combine both streams using `Rx.combineLatest2` or simply use `asyncMap` on the day tasks stream and fetch overdue as a secondary query.

     Implementation approach: Use the dayTasks stream as the primary, and inside asyncMap call the overdue stream's `.first` when isToday. This keeps it simple and follows the existing `dailyPlanProvider` pattern of `stream.asyncMap()`.

3. Add new l10n strings to `lib/l10n/app_de.arb` (add before the closing `}`):
   - `"calendarNoTasks": "Keine Aufgaben"` — shown when a day has no tasks at all
   - `"calendarAllDone": "Alles erledigt!"` — celebration when all tasks for a day are done
   - `"calendarOverdueSection": "Uberfaellig"` — No, reuse existing `dailyPlanSectionOverdue` ("Uberfaellig") for the overdue section header
   - `"calendarTodayButton": "Heute"` — floating today button label

   Actually, we can reuse `dailyPlanSectionOverdue` for the overdue header, `dailyPlanNoTasks` for no-tasks-at-all, and `dailyPlanAllClearTitle`/`dailyPlanAllClearMessage` for celebration. The only truly new string needed is for the Today button:
   - Add `"calendarTodayButton": "Heute"` to the ARB file

4. Run `flutter gen-l10n` to regenerate localization files.
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos CalendarDayState model exists with selectedDate/dayTasks/overdueTasks fields. selectedDateProvider and calendarDayProvider are defined. calendarDayProvider returns overdue tasks only when viewing today. New l10n string "calendarTodayButton" exists. No analysis errors. - `flutter test test/features/home/data/calendar_dao_test.dart` — all DAO tests pass - `flutter analyze --no-fatal-infos` — no errors in new or modified files - `flutter test` — full test suite still passes (existing tests not broken by database.dart changes)

<success_criteria>

  • CalendarDao is registered in AppDatabase and has two working query methods
  • CalendarDayState model correctly separates day tasks from overdue tasks
  • calendarDayProvider returns overdue only for today, not for past/future dates
  • All existing tests still pass after database.dart modification
  • New DAO tests cover core query behaviors </success_criteria>
After completion, create `.planning/phases/05-calendar-strip/05-01-SUMMARY.md`