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 |
|
true |
|
|
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.mdFrom 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
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>