diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md
index e8149d8..417d333 100644
--- a/.planning/REQUIREMENTS.md
+++ b/.planning/REQUIREMENTS.md
@@ -25,6 +25,14 @@ Requirements for milestone v1.2 Polish & Task Management. Each maps to roadmap p
- [x] **CLN-01**: Dead code from v1.0 daily plan (daily_plan_providers.dart, daily_plan_task_row.dart, progress_card.dart) is removed without breaking notification service (DailyPlanDao must be preserved)
+### Tasks Management (Phase 11)
+
+- [ ] **TM-01**: User can check off (complete) a task on any calendar day — checkboxes are never disabled for future dates
+- [ ] **TM-02**: When completing a task on a non-due day, nextDueDate is recalculated from today (not from the original due date)
+- [ ] **TM-03**: Recurring tasks are pre-populated on all applicable days within their current interval window (e.g., a weekly task shows every day in the 7-day window leading up to its due date)
+- [ ] **TM-04**: Pre-populated tasks that have already been completed in the current interval period are hidden from the calendar view
+- [ ] **TM-05**: Pre-populated tasks not yet due have a muted visual distinction (reduced opacity) compared to due-today and overdue tasks
+
## Future Requirements
Deferred to future release. Tracked but not in current roadmap.
@@ -71,21 +79,26 @@ Which phases cover which requirements. Updated during roadmap creation.
| Requirement | Phase | Status |
|-------------|-------|--------|
-| DEL-01 | Phase 8 | Planned |
-| DEL-02 | Phase 8 | Planned |
-| DEL-03 | Phase 8 | Planned |
-| DEL-04 | Phase 8 | Planned |
-| TCX-01 | Phase 9 | Planned |
-| TCX-02 | Phase 9 | Planned |
-| TCX-03 | Phase 9 | Planned |
-| TCX-04 | Phase 9 | Planned |
-| CLN-01 | Phase 10 | Planned |
+| DEL-01 | Phase 8 | Complete |
+| DEL-02 | Phase 8 | Complete |
+| DEL-03 | Phase 8 | Complete |
+| DEL-04 | Phase 8 | Complete |
+| TCX-01 | Phase 9 | Complete |
+| TCX-02 | Phase 9 | Complete |
+| TCX-03 | Phase 9 | Complete |
+| TCX-04 | Phase 9 | Complete |
+| CLN-01 | Phase 10 | Complete |
+| TM-01 | Phase 11 | Planned |
+| TM-02 | Phase 11 | Planned |
+| TM-03 | Phase 11 | Planned |
+| TM-04 | Phase 11 | Planned |
+| TM-05 | Phase 11 | Planned |
**Coverage:**
-- v1.2 requirements: 9 total
-- Mapped to phases: 9
+- v1.2 requirements: 14 total
+- Mapped to phases: 14
- Unmapped: 0
---
*Requirements defined: 2026-03-18*
-*Last updated: 2026-03-18 after roadmap creation (phases 8-10)*
+*Last updated: 2026-03-24 after phase 11 planning*
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 6fd291a..9160e54 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -4,7 +4,7 @@
- ✅ **v1.0 MVP** — Phases 1-4 (shipped 2026-03-16)
- ✅ **v1.1 Calendar & Polish** — Phases 5-7 (shipped 2026-03-16)
-- **v1.2 Polish & Task Management** — Phases 8-10 (in progress)
+- **v1.2 Polish & Task Management** — Phases 8-11 (in progress)
## Phases
@@ -31,11 +31,12 @@ See `milestones/v1.1-ROADMAP.md` for full phase details.
-**v1.2 Polish & Task Management (Phases 8-10):**
+**v1.2 Polish & Task Management (Phases 8-11):**
- [x] **Phase 8: Task Delete** - Add smart delete action to tasks — hard delete if never completed, soft delete (deactivate) if completed at least once (completed 2026-03-18)
- [x] **Phase 9: Task Creation UX** - Rework the frequency picker from flat preset chips to an intuitive "Every N units" interface with quick-select shortcuts (completed 2026-03-18)
- [x] **Phase 10: Dead Code Cleanup** - Remove orphaned v1.0 daily plan files and verify no regressions (completed 2026-03-19)
+- [ ] **Phase 11: Tasks Management** - Allow task checking anytime and pre-populate recurring tasks within their interval window
## Phase Details
@@ -81,6 +82,24 @@ Plans:
3. All 108+ tests pass after cleanup
4. `dart analyze` reports zero issues
+### Phase 11: Tasks Management - Allow task checking anytime and pre-populate recurring tasks
+**Goal**: Users can complete tasks on any day regardless of schedule, and recurring tasks appear on all applicable days within their interval window — making the app feel like a consistent checklist rather than a rigid scheduler
+**Depends on**: Phase 10
+**Requirements**: TM-01, TM-02, TM-03, TM-04, TM-05
+**Plans:** 2 plans
+Plans:
+- [ ] 11-01-PLAN.md — Anytime completion: remove checkbox restrictions, recalculate nextDueDate from today on non-due-day completion
+- [ ] 11-02-PLAN.md — Pre-population: virtual task instances in provider layer, period-completion filtering, muted visual styling
+**Success Criteria** (what must be TRUE):
+ 1. Checkboxes are always enabled on all calendar days (past, today, future) and in room task lists
+ 2. Completing a task on a non-due day recalculates nextDueDate from today, not the original due date
+ 3. A weekly task appears on all 7 days leading up to its due date
+ 4. A monthly task appears on all days within its current month interval
+ 5. Tasks already completed in the current interval period do not reappear as pre-populated
+ 6. Pre-populated tasks have a visually muted appearance (reduced opacity) compared to due-today tasks
+ 7. Overdue tasks retain their existing coral accent styling
+ 8. All tests pass and dart analyze reports zero issues
+
## Progress
| Phase | Milestone | Plans Complete | Status | Completed |
@@ -92,6 +111,7 @@ Plans:
| 5. Calendar Strip | v1.1 | 2/2 | Complete | 2026-03-16 |
| 6. Task History | v1.1 | 1/1 | Complete | 2026-03-16 |
| 7. Task Sorting | v1.1 | 2/2 | Complete | 2026-03-16 |
-| 8. Task Delete | 2/2 | Complete | 2026-03-18 | - |
-| 9. Task Creation UX | 1/1 | Complete | 2026-03-18 | - |
-| 10. Dead Code Cleanup | v1.2 | Complete | 2026-03-19 | 2026-03-19 |
+| 8. Task Delete | v1.2 | 2/2 | Complete | 2026-03-18 |
+| 9. Task Creation UX | v1.2 | 1/1 | Complete | 2026-03-18 |
+| 10. Dead Code Cleanup | v1.2 | 1/1 | Complete | 2026-03-19 |
+| 11. Tasks Management | v1.2 | 0/2 | Planned | - |
diff --git a/.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-01-PLAN.md b/.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-01-PLAN.md
new file mode 100644
index 0000000..e8f30f8
--- /dev/null
+++ b/.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-01-PLAN.md
@@ -0,0 +1,252 @@
+---
+phase: 11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - lib/features/home/presentation/calendar_day_list.dart
+ - lib/features/home/presentation/calendar_task_row.dart
+ - lib/features/tasks/presentation/task_row.dart
+ - lib/features/tasks/data/tasks_dao.dart
+ - test/features/tasks/data/tasks_dao_test.dart
+autonomous: true
+requirements:
+ - TM-01
+ - TM-02
+
+must_haves:
+ truths:
+ - "User can check off a task on any calendar day including future days"
+ - "When completing a task on a non-due day, nextDueDate recalculates from today not from the original due date"
+ - "Overdue tasks remain completable (no regression)"
+ artifacts:
+ - path: "lib/features/home/presentation/calendar_day_list.dart"
+ provides: "Checkbox always enabled for all day tasks"
+ contains: "canComplete: true"
+ - path: "lib/features/home/presentation/calendar_task_row.dart"
+ provides: "CalendarTaskRow with always-enabled checkbox"
+ contains: "canComplete"
+ - path: "lib/features/tasks/presentation/task_row.dart"
+ provides: "TaskRow with always-enabled checkbox"
+ - path: "lib/features/tasks/data/tasks_dao.dart"
+ provides: "completeTask with today-based recalculation"
+ contains: "calculateNextDueDate"
+ key_links:
+ - from: "lib/features/home/presentation/calendar_day_list.dart"
+ to: "CalendarTaskRow"
+ via: "canComplete: true always passed"
+ pattern: "canComplete: true"
+ - from: "lib/features/tasks/data/tasks_dao.dart"
+ to: "scheduling.dart"
+ via: "calculateNextDueDate uses today as base when completing on non-due day"
+ pattern: "calculateNextDueDate"
+---
+
+
+Enable anytime task completion: remove all checkbox-disable restrictions so users can mark tasks done on any calendar day (past, today, or future). When completing a task on a non-due day, recalculate nextDueDate from today (per D-02).
+
+Purpose: Users should never be blocked from marking a task as done. The current behavior of disabling checkboxes for future tasks creates friction and confusion.
+Output: All checkboxes always enabled. completeTask() uses today as base for nextDueDate calculation when task is completed on a non-due day.
+
+
+
+@$HOME/.claude/get-shit-done/workflows/execute-plan.md
+@$HOME/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-CONTEXT.md
+
+
+
+
+From lib/features/home/presentation/calendar_task_row.dart:
+```dart
+class CalendarTaskRow extends StatelessWidget {
+ const CalendarTaskRow({
+ super.key,
+ required this.taskWithRoom,
+ required this.onCompleted,
+ this.isOverdue = false,
+ this.showRoomTag = true,
+ this.canComplete = true, // Currently defaults to true but overridden with !isFuture
+ });
+ final bool canComplete; // When false, checkbox is disabled
+}
+```
+
+From lib/features/tasks/data/tasks_dao.dart:
+```dart
+Future completeTask(int taskId, {DateTime? now}) {
+ // Step 3: calculates next due from task.nextDueDate (original due date)
+ var nextDue = calculateNextDueDate(
+ currentDueDate: task.nextDueDate, // <-- This is the line to change for non-due-day
+ ...
+ );
+}
+```
+
+From lib/features/tasks/domain/scheduling.dart:
+```dart
+DateTime calculateNextDueDate({
+ required DateTime currentDueDate,
+ required IntervalType intervalType,
+ required int intervalDays,
+ int? anchorDay,
+});
+
+DateTime catchUpToPresent({
+ required DateTime nextDue,
+ required DateTime today,
+ required IntervalType intervalType,
+ required int intervalDays,
+ int? anchorDay,
+});
+```
+
+
+
+
+
+
+ Task 1: Remove checkbox-disable restrictions in all three UI files
+
+ lib/features/home/presentation/calendar_day_list.dart
+ lib/features/home/presentation/calendar_task_row.dart
+ lib/features/tasks/presentation/task_row.dart
+
+
+ lib/features/home/presentation/calendar_day_list.dart
+ lib/features/home/presentation/calendar_task_row.dart
+ lib/features/tasks/presentation/task_row.dart
+
+
+**Per D-01: Remove isFuture / canComplete restrictions.**
+
+1. **calendar_day_list.dart** (line ~271): In the `_buildTaskList` method, change the day tasks loop to always pass `canComplete: true`:
+ - Remove the line `final isFuture = state.selectedDate.isAfter(today);` (line ~245)
+ - Change `canComplete: !isFuture` (line ~271) to `canComplete: true`
+ - The `isFuture` variable can be removed entirely since it is only used for `canComplete`
+ - Keep the `today` variable — it is still used for `isToday` check in _buildContent
+
+2. **calendar_task_row.dart**: No changes needed. The `canComplete` parameter already defaults to `true` and the widget itself has no internal disable logic. The restriction was applied by the caller (calendar_day_list.dart).
+
+3. **task_row.dart** (lines ~45, ~60-62): Remove the `isFuture` check that disables the checkbox:
+ - Remove line `final isFuture = dueDate.isAfter(today);` (line ~45)
+ - Change the `onChanged` from the ternary `isFuture ? null : (_) { ... }` to always-enabled:
+ ```dart
+ onChanged: (_) {
+ ref.read(taskActionsProvider.notifier).completeTask(task.id);
+ },
+ ```
+ - The `dueDate` and `isOverdue` variables remain — they are used for styling the relative date text color
+
+
+ grep -n "isFuture" lib/features/home/presentation/calendar_day_list.dart lib/features/tasks/presentation/task_row.dart; echo "---"; grep -n "canComplete: true" lib/features/home/presentation/calendar_day_list.dart
+
+
+ - calendar_day_list.dart does NOT contain the string `isFuture`
+ - calendar_day_list.dart contains `canComplete: true` in the _buildAnimatedTaskRow call for dayTasks
+ - task_row.dart does NOT contain the string `isFuture`
+ - task_row.dart does NOT contain `? null` in the Checkbox onChanged handler
+ - calendar_task_row.dart is unchanged (canComplete param still exists with default true)
+
+ All checkboxes are always enabled across calendar and task list views. No isFuture guard remains in UI code.
+
+
+
+ Task 2: Update completeTask to recalculate nextDueDate from today on non-due-day completion
+
+ lib/features/tasks/data/tasks_dao.dart
+ test/features/tasks/data/tasks_dao_test.dart
+
+
+ lib/features/tasks/data/tasks_dao.dart
+ lib/features/tasks/domain/scheduling.dart
+ test/features/tasks/data/tasks_dao_test.dart
+
+
+ - Test: Completing a task ON its due date recalculates nextDueDate from the original due date (existing behavior preserved)
+ - Test: Completing a weekly task 3 days BEFORE its due date recalculates nextDueDate from today (not original due date) — e.g., task due Friday, completed Tuesday, next due = next Tuesday
+ - Test: Completing a daily task on a non-due day still produces tomorrow as next due
+ - Test: Completing a monthly task early recalculates from today with anchor day preserved
+
+
+**Per D-02 and D-03: When completing a task on a non-due day, recalculate nextDueDate from today.**
+
+In `lib/features/tasks/data/tasks_dao.dart`, method `completeTask()`:
+
+Change step 3 from:
+```dart
+// 3. Calculate next due date (from original due date, not today)
+var nextDue = calculateNextDueDate(
+ currentDueDate: task.nextDueDate,
+ intervalType: task.intervalType,
+ intervalDays: task.intervalDays,
+ anchorDay: task.anchorDay,
+);
+```
+
+To:
+```dart
+// 3. Calculate next due date
+// If completing on the due date, use original due date as base (keeps rhythm).
+// If completing on a different day (early or late), use today as base (per D-02).
+final todayStart = DateTime(currentTime.year, currentTime.month, currentTime.day);
+final taskDueDay = DateTime(task.nextDueDate.year, task.nextDueDate.month, task.nextDueDate.day);
+final baseDate = todayStart == taskDueDay ? task.nextDueDate : todayStart;
+
+var nextDue = calculateNextDueDate(
+ currentDueDate: baseDate,
+ intervalType: task.intervalType,
+ intervalDays: task.intervalDays,
+ anchorDay: task.anchorDay,
+);
+```
+
+Note: The existing `todayDateOnly` variable (line 66-70) can be replaced by `todayStart` since they are the same. Rename to avoid duplication. The catch-up step (step 4) remains unchanged — it still ensures nextDue is not in the past.
+
+Write 4 new test cases in `test/features/tasks/data/tasks_dao_test.dart`:
+1. `completeTask on due date preserves rhythm` — weekly task due 2026-03-24, complete on 2026-03-24, next due = 2026-03-31
+2. `completeTask before due date recalculates from today` — weekly task due 2026-03-28, complete on 2026-03-24, next due = 2026-03-31 (7 days from today)
+3. `completeTask daily task on non-due day` — daily task due 2026-03-26, complete on 2026-03-24, next due = 2026-03-25 (tomorrow)
+4. `completeTask monthly task early preserves anchor` — monthly task due 2026-03-28 anchorDay=28, complete on 2026-03-24, next due = 2026-04-28
+
+
+ cd /home/jean-luc-makiola/Development/projects/HouseHoldKeaper && flutter test test/features/tasks/data/tasks_dao_test.dart --reporter compact
+
+
+ - tasks_dao.dart completeTask() contains `final baseDate = todayStart == taskDueDay ? task.nextDueDate : todayStart;`
+ - tasks_dao.dart completeTask() passes `currentDueDate: baseDate` to calculateNextDueDate
+ - tasks_dao_test.dart contains test with name matching `completeTask.*before due date.*recalculates from today`
+ - tasks_dao_test.dart contains test with name matching `completeTask.*on due date.*preserves rhythm`
+ - All tests in tasks_dao_test.dart pass (exit code 0)
+
+ completeTask() uses today as base for non-due-day completions. 4 new tests verify the behavior for on-due-day, before-due-day, daily, and monthly scenarios. All existing tests still pass.
+
+
+
+
+
+1. `flutter test` — all existing + new tests pass
+2. `dart analyze` — zero issues
+3. `grep -rn "isFuture" lib/features/home/presentation/calendar_day_list.dart lib/features/tasks/presentation/task_row.dart` — no matches
+4. `grep -n "canComplete: true" lib/features/home/presentation/calendar_day_list.dart` — found in dayTasks loop
+
+
+
+- Checkboxes are always enabled on all calendar days (past, today, future)
+- Checkboxes are always enabled in room task list view
+- Completing a task on its due date preserves the original interval rhythm
+- Completing a task on a non-due day recalculates from today
+- All existing tests pass with zero regressions
+- dart analyze reports zero issues
+
+
+
diff --git a/.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-02-PLAN.md b/.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-02-PLAN.md
new file mode 100644
index 0000000..b6864ae
--- /dev/null
+++ b/.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-02-PLAN.md
@@ -0,0 +1,566 @@
+---
+phase: 11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks
+plan: 02
+type: execute
+wave: 2
+depends_on: ["11-01"]
+files_modified:
+ - lib/features/home/data/calendar_dao.dart
+ - lib/features/home/presentation/calendar_providers.dart
+ - lib/features/home/domain/calendar_models.dart
+ - lib/features/home/presentation/calendar_day_list.dart
+ - lib/features/home/presentation/calendar_task_row.dart
+ - test/features/home/data/calendar_dao_test.dart
+autonomous: true
+requirements:
+ - TM-03
+ - TM-04
+ - TM-05
+
+must_haves:
+ truths:
+ - "A weekly task appears on every day within its current interval window, not just on the due date"
+ - "A monthly task appears on every day within its current interval window"
+ - "A daily task appears every day"
+ - "Tasks already completed in the current interval period do not reappear as pre-populated"
+ - "Pre-populated tasks that are not yet due have a muted/lighter visual appearance"
+ - "Tasks on their actual due date appear with full styling"
+ - "Overdue tasks keep their existing red/orange accent"
+ artifacts:
+ - path: "lib/features/home/data/calendar_dao.dart"
+ provides: "watchAllActiveRecurringTasks query and watchCompletionsInRange query"
+ contains: "watchAllActiveRecurringTasks"
+ - path: "lib/features/home/presentation/calendar_providers.dart"
+ provides: "Pre-population logic combining due-today + virtual instances + overdue"
+ contains: "isPrePopulated"
+ - path: "lib/features/home/domain/calendar_models.dart"
+ provides: "CalendarDayState with pre-populated task support"
+ contains: "prePopulatedTasks"
+ - path: "lib/features/home/presentation/calendar_task_row.dart"
+ provides: "Visual distinction for pre-populated tasks"
+ contains: "isPrePopulated"
+ - path: "lib/features/home/presentation/calendar_day_list.dart"
+ provides: "Renders pre-populated section with muted styling"
+ contains: "prePopulatedTasks"
+ key_links:
+ - from: "lib/features/home/presentation/calendar_providers.dart"
+ to: "lib/features/home/data/calendar_dao.dart"
+ via: "watchAllActiveRecurringTasks stream for pre-population source data"
+ pattern: "watchAllActiveRecurringTasks"
+ - from: "lib/features/home/presentation/calendar_providers.dart"
+ to: "lib/features/tasks/domain/scheduling.dart"
+ via: "calculateNextDueDate used to determine interval window boundaries"
+ pattern: "calculateNextDueDate"
+ - from: "lib/features/home/presentation/calendar_day_list.dart"
+ to: "CalendarTaskRow"
+ via: "isPrePopulated flag passed for visual distinction"
+ pattern: "isPrePopulated"
+---
+
+
+Pre-populate recurring tasks on all applicable days within their interval window. A weekly task due Monday shows on all 7 days leading up to that Monday. Tasks already completed in the current period are hidden. Pre-populated (not-yet-due) tasks get a muted visual style.
+
+Purpose: Users want a consistent checklist — tasks should be visible and completable before their due date, not appear only when due. This makes the app feel like a reliable weekly/monthly checklist.
+Output: Virtual task instances in provider layer, period-completion filtering, muted visual styling for upcoming tasks.
+
+
+
+@$HOME/.claude/get-shit-done/workflows/execute-plan.md
+@$HOME/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-CONTEXT.md
+@.planning/phases/11-issue-3-tasks-management-allow-task-checking-anytime-and-pre-populate-recurring-tasks/11-01-SUMMARY.md
+
+
+
+
+From lib/features/home/domain/daily_plan_models.dart:
+```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/domain/calendar_models.dart:
+```dart
+class CalendarDayState {
+ final DateTime selectedDate;
+ final List dayTasks;
+ final List overdueTasks;
+ final int totalTaskCount;
+ bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty;
+}
+```
+
+From lib/features/tasks/domain/frequency.dart:
+```dart
+enum IntervalType {
+ daily, everyNDays, weekly, biweekly, monthly, everyNMonths, quarterly, yearly,
+}
+```
+
+From lib/features/tasks/domain/scheduling.dart:
+```dart
+DateTime calculateNextDueDate({
+ required DateTime currentDueDate,
+ required IntervalType intervalType,
+ required int intervalDays,
+ int? anchorDay,
+});
+```
+
+From lib/features/home/data/calendar_dao.dart:
+```dart
+class CalendarDao extends DatabaseAccessor with _$CalendarDaoMixin {
+ Stream> watchTasksForDate(DateTime date);
+ Stream> watchOverdueTasks(DateTime referenceDate);
+ // + room-scoped variants
+}
+```
+
+From lib/core/database/database.dart (Tasks table):
+```dart
+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)();
+ IntColumn get intervalType => intEnum()();
+ IntColumn get intervalDays => integer().withDefault(const Constant(1))();
+ IntColumn get anchorDay => integer().nullable()();
+ DateTimeColumn get nextDueDate => dateTime()();
+ BoolColumn get isActive => BoolColumn().withDefault(const Constant(true))();
+}
+```
+
+From lib/core/database/database.dart (TaskCompletions table):
+```dart
+class TaskCompletions extends Table {
+ IntColumn get id => integer().autoIncrement()();
+ IntColumn get taskId => integer().references(Tasks, #id)();
+ DateTimeColumn get completedAt => dateTime()();
+}
+```
+
+
+
+
+
+
+ Task 1: Add DAO queries, update CalendarDayState model, and implement pre-population provider logic
+
+ lib/features/home/data/calendar_dao.dart
+ lib/features/home/domain/calendar_models.dart
+ lib/features/home/presentation/calendar_providers.dart
+ test/features/home/data/calendar_dao_test.dart
+
+
+ lib/features/home/data/calendar_dao.dart
+ lib/features/home/domain/calendar_models.dart
+ lib/features/home/presentation/calendar_providers.dart
+ lib/features/tasks/domain/scheduling.dart
+ lib/features/tasks/domain/frequency.dart
+ lib/core/database/database.dart
+ test/features/home/data/calendar_dao_test.dart
+
+
+**Per D-04 through D-10: Query-time virtual instances, no schema migration.**
+
+### Step 1: New DAO methods in calendar_dao.dart
+
+Add two new methods to `CalendarDao`:
+
+**1a. `watchAllActiveRecurringTasks()`** — Fetch ALL active tasks with their rooms (for pre-population logic):
+```dart
+Stream> watchAllActiveRecurringTasks() {
+ final query = select(tasks).join([
+ innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
+ ]);
+ query.where(tasks.isActive.equals(true));
+ query.orderBy([OrderingTerm.asc(tasks.name)]);
+
+ return query.watch().map((rows) {
+ return rows.map((row) {
+ final task = row.readTable(tasks);
+ final room = row.readTable(rooms);
+ return TaskWithRoom(task: task, roomName: room.name, roomId: room.id);
+ }).toList();
+ });
+}
+```
+
+**1b. `watchCompletionsInRange(int taskId, DateTime start, DateTime end)`** — Check if a task was completed within a date range (for period-completion filtering per D-09):
+```dart
+Stream> watchCompletionsInRange(int taskId, DateTime start, DateTime end) {
+ return (select(taskCompletions)
+ ..where((c) =>
+ c.taskId.equals(taskId) &
+ c.completedAt.isBiggerOrEqualValue(start) &
+ c.completedAt.isSmallerThanValue(end)))
+ .watch();
+}
+```
+
+**1c. Room-scoped variant `watchAllActiveRecurringTasksInRoom(int roomId)`**:
+Same as 1a but with additional `.where(tasks.roomId.equals(roomId))`.
+
+### Step 2: Update CalendarDayState in calendar_models.dart
+
+Add a `prePopulatedTasks` field:
+```dart
+class CalendarDayState {
+ final DateTime selectedDate;
+ final List dayTasks;
+ final List overdueTasks;
+ final List prePopulatedTasks; // NEW — tasks visible via pre-population
+ final int totalTaskCount;
+
+ const CalendarDayState({
+ required this.selectedDate,
+ required this.dayTasks,
+ required this.overdueTasks,
+ this.prePopulatedTasks = const [], // Default empty for backward compat
+ required this.totalTaskCount,
+ });
+
+ bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty && prePopulatedTasks.isEmpty;
+}
+```
+
+### Step 3: Rewrite calendarDayProvider in calendar_providers.dart
+
+The provider needs to combine three streams: due-today tasks, overdue tasks, and pre-populated virtual tasks.
+
+Add a top-level helper function `_isInCurrentIntervalWindow` that determines if a task should appear on a given date:
+
+```dart
+/// Determines whether [task] should appear on [selectedDate] via pre-population.
+///
+/// Per D-07: A task shows on all days within the current interval window
+/// leading up to its nextDueDate. For example:
+/// - weekly task due Monday: shows on all 7 days (Tue-Mon) before nextDueDate
+/// - monthly task due 15th: shows on all ~30 days leading up to the 15th
+/// - daily task: shows every day (interval window = 1 day, always matches)
+bool _isInCurrentIntervalWindow(Task task, DateTime selectedDate) {
+ final dueDate = DateTime(task.nextDueDate.year, task.nextDueDate.month, task.nextDueDate.day);
+ final selected = DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
+
+ // Task is not yet due or due today — it might be in the window
+ // If selected date IS the due date, it is a "due today" task (not pre-populated)
+ if (selected == dueDate) return false;
+ // If selected date is after the due date, task is overdue (handled separately)
+ if (selected.isAfter(dueDate)) return false;
+
+ // Calculate the start of the current interval window:
+ // The previous due date = current nextDueDate minus one interval
+ final previousDue = _calculatePreviousDueDate(task);
+ final prevDay = DateTime(previousDue.year, previousDue.month, previousDue.day);
+
+ // Selected date must be AFTER the previous due date (exclusive)
+ // and BEFORE the next due date (exclusive — due date itself is "dayTasks" not pre-pop)
+ return selected.isAfter(prevDay) && selected.isBefore(dueDate);
+}
+```
+
+Add `_calculatePreviousDueDate` helper:
+```dart
+/// Reverse-calculate the previous due date by subtracting one interval.
+/// This gives the start of the current interval window.
+DateTime _calculatePreviousDueDate(Task task) {
+ switch (task.intervalType) {
+ case IntervalType.daily:
+ return task.nextDueDate.subtract(const Duration(days: 1));
+ case IntervalType.everyNDays:
+ return task.nextDueDate.subtract(Duration(days: task.intervalDays));
+ case IntervalType.weekly:
+ return task.nextDueDate.subtract(const Duration(days: 7));
+ case IntervalType.biweekly:
+ return task.nextDueDate.subtract(const Duration(days: 14));
+ case IntervalType.monthly:
+ return _subtractMonths(task.nextDueDate, 1, task.anchorDay);
+ case IntervalType.everyNMonths:
+ return _subtractMonths(task.nextDueDate, task.intervalDays, task.anchorDay);
+ case IntervalType.quarterly:
+ return _subtractMonths(task.nextDueDate, 3, task.anchorDay);
+ case IntervalType.yearly:
+ return _subtractMonths(task.nextDueDate, 12, task.anchorDay);
+ }
+}
+
+DateTime _subtractMonths(DateTime date, int months, int? anchorDay) {
+ final targetMonth = date.month - months;
+ final targetYear = date.year + (targetMonth - 1) ~/ 12;
+ final normalizedMonth = ((targetMonth - 1) % 12) + 1;
+ final day = anchorDay ?? date.day;
+ final lastDay = DateTime(targetYear, normalizedMonth + 1, 0).day;
+ final clampedDay = day > lastDay ? lastDay : day;
+ return DateTime(targetYear, normalizedMonth, clampedDay);
+}
+```
+
+Rewrite `calendarDayProvider` to:
+1. Watch `watchAllActiveRecurringTasks()` alongside `watchTasksForDate()`
+2. Filter all tasks through `_isInCurrentIntervalWindow()` to get pre-populated candidates
+3. For each candidate, check if completed in current period via `watchCompletionsInRange()` (per D-09, D-10)
+4. Exclude tasks already in `dayTasks` (they appear as due-today, not pre-populated)
+5. Exclude tasks already in `overdueTasks`
+6. Return combined state with new `prePopulatedTasks` list
+
+```dart
+final calendarDayProvider =
+ StreamProvider.autoDispose((ref) {
+ final db = ref.watch(appDatabaseProvider);
+ final selectedDate = ref.watch(selectedDateProvider);
+ final sortOption = ref.watch(sortPreferenceProvider);
+
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final isToday = selectedDate == today;
+
+ final dayTasksStream = db.calendarDao.watchTasksForDate(selectedDate);
+ final allTasksStream = db.calendarDao.watchAllActiveRecurringTasks();
+
+ // Combine both streams
+ return dayTasksStream.asyncMap((dayTasks) async {
+ final List overdueTasks;
+ if (isToday) {
+ overdueTasks =
+ await db.calendarDao.watchOverdueTasks(selectedDate).first;
+ } else {
+ overdueTasks = const [];
+ }
+
+ // Get all active tasks for pre-population filtering
+ final allTasks = await allTasksStream.first;
+
+ // IDs of tasks already showing as due-today or overdue
+ final dueTodayIds = dayTasks.map((t) => t.task.id).toSet();
+ final overdueIds = overdueTasks.map((t) => t.task.id).toSet();
+
+ // Filter for pre-populated tasks
+ final prePopulated = [];
+ for (final tw in allTasks) {
+ // Skip if already showing as due-today or overdue
+ if (dueTodayIds.contains(tw.task.id)) continue;
+ if (overdueIds.contains(tw.task.id)) continue;
+
+ // Check if in current interval window
+ if (!_isInCurrentIntervalWindow(tw.task, selectedDate)) continue;
+
+ // Check if already completed in current period (D-09, D-10)
+ final prevDue = _calculatePreviousDueDate(tw.task);
+ final completions = await db.calendarDao
+ .watchCompletionsInRange(
+ tw.task.id,
+ DateTime(prevDue.year, prevDue.month, prevDue.day),
+ DateTime(tw.task.nextDueDate.year, tw.task.nextDueDate.month, tw.task.nextDueDate.day).add(const Duration(days: 1)),
+ )
+ .first;
+
+ if (completions.isEmpty) {
+ prePopulated.add(tw);
+ }
+ }
+
+ final totalTaskCount = await db.calendarDao.getTaskCount();
+
+ return CalendarDayState(
+ selectedDate: selectedDate,
+ dayTasks: _sortTasks(dayTasks, sortOption),
+ overdueTasks: overdueTasks,
+ prePopulatedTasks: _sortTasks(prePopulated, sortOption),
+ totalTaskCount: totalTaskCount,
+ );
+ });
+});
+```
+
+Apply the SAME changes to `roomCalendarDayProvider`, but use `watchAllActiveRecurringTasksInRoom(roomId)` instead of `watchAllActiveRecurringTasks()`.
+
+### Step 4: Add DAO tests
+
+Add tests to `test/features/home/data/calendar_dao_test.dart`:
+1. `watchAllActiveRecurringTasks returns all active tasks` — insert 3 tasks (2 active, 1 inactive), verify 2 returned
+2. `watchCompletionsInRange returns completions within date range` — insert completions at various dates, verify only in-range ones returned
+3. `watchAllActiveRecurringTasksInRoom filters by room` — insert tasks in 2 rooms, verify room filter works
+
+
+ cd /home/jean-luc-makiola/Development/projects/HouseHoldKeaper && flutter test test/features/home/data/calendar_dao_test.dart --reporter compact
+
+
+ - calendar_dao.dart contains method `watchAllActiveRecurringTasks()`
+ - calendar_dao.dart contains method `watchCompletionsInRange(`
+ - calendar_dao.dart contains method `watchAllActiveRecurringTasksInRoom(`
+ - calendar_models.dart CalendarDayState contains field `List prePopulatedTasks`
+ - calendar_models.dart isEmpty getter includes `prePopulatedTasks.isEmpty`
+ - calendar_providers.dart contains function `_isInCurrentIntervalWindow(`
+ - calendar_providers.dart contains function `_calculatePreviousDueDate(`
+ - calendar_providers.dart calendarDayProvider passes `prePopulatedTasks:` to CalendarDayState
+ - calendar_providers.dart roomCalendarDayProvider passes `prePopulatedTasks:` to CalendarDayState
+ - calendar_dao_test.dart contains test matching `watchAllActiveRecurringTasks`
+ - calendar_dao_test.dart contains test matching `watchCompletionsInRange`
+ - All tests in calendar_dao_test.dart pass (exit code 0)
+
+ DAO provides all-active-tasks query and completion-range query. Provider computes virtual pre-populated task instances using interval window logic. Completed-in-current-period tasks are excluded. CalendarDayState carries prePopulatedTasks. All tests pass.
+
+
+
+ Task 2: Render pre-populated tasks with muted visual distinction in calendar UI
+
+ lib/features/home/presentation/calendar_day_list.dart
+ lib/features/home/presentation/calendar_task_row.dart
+
+
+ lib/features/home/presentation/calendar_day_list.dart
+ lib/features/home/presentation/calendar_task_row.dart
+ lib/features/home/domain/calendar_models.dart
+
+
+**Per D-11, D-12, D-13: Visual distinction for pre-populated tasks.**
+
+### Step 1: Add `isPrePopulated` prop to CalendarTaskRow
+
+In `calendar_task_row.dart`, add an `isPrePopulated` parameter:
+```dart
+class CalendarTaskRow extends StatelessWidget {
+ const CalendarTaskRow({
+ super.key,
+ required this.taskWithRoom,
+ required this.onCompleted,
+ this.isOverdue = false,
+ this.showRoomTag = true,
+ this.canComplete = true,
+ this.isPrePopulated = false, // NEW
+ });
+
+ final bool isPrePopulated; // NEW
+```
+
+In the `build` method, apply muted styling when `isPrePopulated` is true:
+- Wrap the entire `ListTile` in an `Opacity` widget with `opacity: isPrePopulated ? 0.55 : 1.0`
+- This gives pre-populated tasks a subtle "upcoming, not yet due" appearance per D-11
+- Overdue tasks (`isOverdue: true`) are never pre-populated, so no conflict with D-13
+- Due-today tasks are not pre-populated either, so full styling is preserved per D-12
+
+```dart
+@override
+Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final task = taskWithRoom.task;
+
+ final tile = ListTile(
+ // ... existing ListTile code unchanged ...
+ );
+
+ return isPrePopulated ? Opacity(opacity: 0.55, child: tile) : tile;
+}
+```
+
+### Step 2: Render pre-populated tasks in CalendarDayList
+
+In `calendar_day_list.dart`, update `_buildTaskList` to include pre-populated tasks:
+
+After the day tasks loop and before the `return ListView(children: items);`, add:
+```dart
+// Pre-populated tasks section (upcoming tasks within interval window).
+if (state.prePopulatedTasks.isNotEmpty) {
+ items.add(_buildSectionHeader('Demnächst', theme,
+ color: theme.colorScheme.onSurface.withValues(alpha: 0.5)));
+ for (final tw in state.prePopulatedTasks) {
+ items.add(_buildAnimatedTaskRow(
+ tw,
+ isOverdue: false,
+ showRoomTag: showRoomTag,
+ canComplete: true,
+ isPrePopulated: true,
+ ));
+ }
+}
+```
+
+Update `_buildAnimatedTaskRow` to accept and pass `isPrePopulated`:
+```dart
+Widget _buildAnimatedTaskRow(
+ TaskWithRoom tw, {
+ required bool isOverdue,
+ required bool showRoomTag,
+ required bool canComplete,
+ bool isPrePopulated = false,
+}) {
+ // ... existing completing animation check ...
+
+ return CalendarTaskRow(
+ key: ValueKey('task-${tw.task.id}'),
+ taskWithRoom: tw,
+ isOverdue: isOverdue,
+ showRoomTag: showRoomTag,
+ canComplete: canComplete,
+ isPrePopulated: isPrePopulated,
+ onCompleted: () => _onTaskCompleted(tw.task.id),
+ );
+}
+```
+
+Also update `_CompletingTaskRow` to pass `isPrePopulated: false` (completing tasks are always full-styled).
+
+### Step 3: Update celebration state logic
+
+In `_buildContent`, update the celebration check to also verify prePopulatedTasks is empty:
+```dart
+if (isToday && state.dayTasks.isEmpty && state.overdueTasks.isEmpty && state.prePopulatedTasks.isEmpty && state.totalTaskCount > 0) {
+ return _buildCelebration(l10n, theme);
+}
+```
+
+### Step 4: Section header "Demnächst" (German for "Coming up")
+
+The section header text `'Demnächst'` is hardcoded for now. If a localization key is preferred, add `calendarPrePopulatedSection` to the l10n strings. For consistency with the existing pattern of using `l10n.dailyPlanSectionOverdue` for the overdue header, add a new key. However, the CONTEXT.md does not mandate localization changes, so inline German string is acceptable.
+
+
+ cd /home/jean-luc-makiola/Development/projects/HouseHoldKeaper && dart analyze lib/features/home/presentation/calendar_day_list.dart lib/features/home/presentation/calendar_task_row.dart
+
+
+ - calendar_task_row.dart contains `final bool isPrePopulated;`
+ - calendar_task_row.dart contains `this.isPrePopulated = false`
+ - calendar_task_row.dart contains `Opacity(opacity: 0.55` or `opacity: isPrePopulated ? 0.55 : 1.0`
+ - calendar_day_list.dart contains `state.prePopulatedTasks.isNotEmpty`
+ - calendar_day_list.dart contains string `'Demnächst'`
+ - calendar_day_list.dart contains `isPrePopulated: true` in the pre-populated tasks loop
+ - calendar_day_list.dart _buildAnimatedTaskRow signature contains `bool isPrePopulated`
+ - dart analyze reports zero issues for both files
+
+ Pre-populated tasks render below day tasks with "Demnächst" section header. They have 0.55 opacity for visual distinction. Due-today tasks have full styling. Overdue tasks keep coral accent. All checkboxes functional. dart analyze clean.
+
+
+
+
+
+1. `flutter test` — ALL tests pass (existing + new)
+2. `dart analyze` — zero issues across entire project
+3. `grep -n "prePopulatedTasks" lib/features/home/domain/calendar_models.dart lib/features/home/presentation/calendar_providers.dart lib/features/home/presentation/calendar_day_list.dart` — found in all three files
+4. `grep -n "isPrePopulated" lib/features/home/presentation/calendar_task_row.dart lib/features/home/presentation/calendar_day_list.dart` — found in both files
+5. `grep -n "watchAllActiveRecurringTasks" lib/features/home/data/calendar_dao.dart` — method exists
+6. `grep -n "watchCompletionsInRange" lib/features/home/data/calendar_dao.dart` — method exists
+
+
+
+- Weekly tasks appear on all 7 days leading up to their due date
+- Monthly tasks appear on all days within their current month interval
+- Daily tasks appear every day (since their interval window is 1 day, they are always "due today" and show as dayTasks, not pre-populated)
+- Completing a pre-populated task triggers normal completeTask flow and the task disappears from remaining days in the period
+- Pre-populated tasks have a muted 0.55 opacity appearance
+- Due-today tasks show with full styling
+- Overdue tasks keep coral color
+- All tests pass, dart analyze clean
+
+
+