diff --git a/.planning/phases/03-daily-plan-and-cleanliness/03-RESEARCH.md b/.planning/phases/03-daily-plan-and-cleanliness/03-RESEARCH.md
new file mode 100644
index 0000000..9620063
--- /dev/null
+++ b/.planning/phases/03-daily-plan-and-cleanliness/03-RESEARCH.md
@@ -0,0 +1,695 @@
+# Phase 3: Daily Plan and Cleanliness - Research
+
+**Researched:** 2026-03-16
+**Domain:** Flutter daily plan screen with cross-room Drift queries, animated task completion, sectioned list UI, progress indicators
+**Confidence:** HIGH
+
+## Summary
+
+Phase 3 transforms the placeholder Home tab into the app's primary "daily plan" screen -- the first thing users see when opening HouseHoldKeaper. The screen needs three key capabilities: (1) a cross-room Drift query that watches all tasks and categorizes them by due date (overdue, today, tomorrow), (2) animated task removal on checkbox completion with the existing `TasksDao.completeTask()` logic, and (3) a progress indicator card showing "X von Y erledigt" that updates in real-time.
+
+The existing codebase provides strong foundations: `TasksDao.completeTask()` already handles completion + scheduling in a single transaction, `formatRelativeDate()` produces German date labels, `TaskRow` provides the baseline task row widget (needs adaptation), and the Riverpod stream provider pattern is well-established. The main new work is: (a) a new DAO method that joins tasks with rooms for cross-room queries, (b) a `DailyPlanTaskRow` widget variant with room name tag and no row-tap navigation, (c) `AnimatedList` for slide-out completion animation, (d) `ExpansionTile` for the collapsible "Demnachst" section, and (e) new localization strings.
+
+CLEAN-01 (cleanliness indicator on room cards) is already fully implemented in Phase 2 via `RoomWithStats.cleanlinessRatio` and the `LinearProgressIndicator` bar at the bottom of `RoomCard`. This requirement needs only verification, not new implementation.
+
+**Primary recommendation:** Add a single new DAO method `watchAllTasksWithRoomName()` that joins tasks with rooms, expose it through a manual `StreamProvider` (same pattern as `tasksInRoomProvider` due to drift type issues), derive overdue/today/tomorrow categorization in the provider layer, and build the daily plan screen as a `CustomScrollView` with `SliverAnimatedList` for animated completion.
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+- **Daily plan screen structure**: Single scroll list with three section headers: Uberfaellig, Heute, Demnachst. Flat task list within each section -- tasks are not grouped under room sub-headers. Each task row shows room name as an inline tappable tag that navigates to that room's task list. Progress indicator at the very top as a prominent card/banner ("5 von 12 erledigt") -- first thing the user sees. Overdue section only appears when there are overdue tasks. Demnachst section is collapsed by default -- shows header with count (e.g. "Demnachst (4)"), expands on tap. PLAN-01 "grouped by room" is satisfied by room name shown on each task -- not visual sub-grouping.
+- **Task completion on daily plan**: Checkbox only -- no swipe-to-complete gesture. Consistent with Phase 2 room task list. Completed tasks animate out of the list (slide away). Progress counter updates immediately. No navigation from tapping task rows -- the daily plan is a focused "get things done" screen. Only the checkbox and the room name tag are interactive. Completion behavior identical to Phase 2: immediate, no undo, records timestamp, auto-calculates next due date.
+- **Upcoming tasks scope**: Tomorrow only -- Demnachst shows tasks due the next calendar day. Read-only preview -- no checkboxes, tasks cannot be completed ahead of schedule from the daily plan. Collapsed by default to keep focus on today's actionable tasks.
+
+### Claude's Discretion
+- "All clear" empty state design (follow Phase 1's playful, emoji-friendly German tone with the established visual pattern: Material icon + message + optional action)
+- Task row adaptation for daily plan context (may differ from TaskRow in room view since no row-tap navigation and room name tag is added)
+- Exact animation for task completion (slide direction, duration, easing)
+- Progress card/banner visual design (linear progress bar, circular, or text-only)
+- Section header styling and the collapsed/expanded toggle for Demnachst
+- How overdue tasks are sorted within the flat list (most overdue first, or by room, or alphabetical)
+
+### Deferred Ideas (OUT OF SCOPE)
+None -- discussion stayed within phase scope
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| PLAN-01 | User sees all tasks due today grouped by room on the daily plan screen | New DAO join query `watchAllTasksWithRoomName()`, flat list with room name tag on each row satisfies "grouped by room" per CONTEXT.md |
+| PLAN-02 | Overdue tasks appear in a separate highlighted section at the top | Provider-layer date categorization splits tasks into overdue/today/tomorrow sections; overdue section conditionally rendered |
+| PLAN-03 | User can preview upcoming tasks (tomorrow) | Demnachst section with `ExpansionTile` collapsed by default, read-only rows (no checkbox) |
+| PLAN-04 | User can checkbox to mark tasks done from daily plan | Reuse existing `taskActionsProvider.completeTask()`, `AnimatedList.removeItem()` for slide-out animation |
+| PLAN-05 | Progress indicator showing completed vs total tasks today | Computed from stream data: `completedToday` count from `TaskCompletions` + `totalToday` from due tasks. Progress card at top of screen |
+| PLAN-06 | "All clear" empty state when no tasks are due | Established empty state pattern (Material icon + message + optional action) in German |
+| CLEAN-01 | Each room card displays cleanliness indicator | Already implemented in Phase 2 via `RoomWithStats.cleanlinessRatio` and `RoomCard` -- verification only |
+
+
+## Standard Stack
+
+### Core (already in project)
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| drift | 2.31.0 | Type-safe SQLite ORM with join queries | Already established; provides `leftOuterJoin`, `.watch()` streams for cross-room task queries |
+| flutter_riverpod | 3.3.1 | State management | Already established; `StreamProvider` pattern for reactive daily plan data |
+| riverpod_annotation | 4.0.2 | Provider code generation | `@riverpod` for generated providers |
+| go_router | 17.1.0 | Declarative routing | Room name tag navigation uses `context.go('/rooms/$roomId')` |
+| flutter (SDK) | 3.41.1 | Framework | Provides `AnimatedList`, `ExpansionTile`, `LinearProgressIndicator` |
+
+### New Dependencies
+None. Phase 3 uses only Flutter built-in widgets and existing project dependencies.
+
+### Alternatives Considered
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| `AnimatedList` with manual state sync | Simple `ListView` with `AnimatedSwitcher` | `AnimatedList` provides proper slide-out with `removeItem()`. `AnimatedSwitcher` only fades, no size collapse. Use `AnimatedList`. |
+| `ExpansionTile` for Demnachst | Custom `AnimatedContainer` | `ExpansionTile` is Material 3 native, handles expand/collapse animation, arrow icon, and state automatically. No reason to hand-roll. |
+| `LinearProgressIndicator` in card | `CircularProgressIndicator` or custom radial | Linear is simpler, more glanceable for "X von Y" context, and matches the progress bar pattern already on room cards. Use linear. |
+| Drift join query | In-memory join via multiple stream providers | Drift join runs in SQLite, more efficient for large task counts, and produces a single reactive stream. In-memory join requires watching two streams and combining, more complex. |
+
+## Architecture Patterns
+
+### Recommended Project Structure
+```
+lib/
+ features/
+ home/
+ data/
+ daily_plan_dao.dart # New DAO for cross-room task queries
+ daily_plan_dao.g.dart
+ domain/
+ daily_plan_models.dart # TaskWithRoom data class, DailyPlanState
+ presentation/
+ home_screen.dart # Replace current placeholder (COMPLETE REWRITE)
+ daily_plan_providers.dart # Riverpod providers for daily plan data
+ daily_plan_providers.g.dart
+ daily_plan_task_row.dart # Task row variant for daily plan context
+ progress_card.dart # "X von Y erledigt" progress banner
+ tasks/
+ data/
+ tasks_dao.dart # Unchanged -- reuse completeTask()
+ presentation/
+ task_providers.dart # Unchanged -- reuse taskActionsProvider
+ l10n/
+ app_de.arb # Add ~10 new localization keys
+```
+
+### Pattern 1: Drift Join Query for Tasks with Room Name
+**What:** A DAO method that joins the tasks table with rooms to produce task objects paired with their room name, watched as a reactive stream.
+**When to use:** Daily plan needs all tasks across all rooms with room name for display.
+**Example:**
+```dart
+// Source: drift.simonbinder.eu/dart_api/select/ (join documentation)
+
+/// A task paired with its room name for daily plan display.
+class TaskWithRoom {
+ final Task task;
+ final String roomName;
+ final int roomId;
+
+ const TaskWithRoom({
+ required this.task,
+ required this.roomName,
+ required this.roomId,
+ });
+}
+
+@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])
+class DailyPlanDao extends DatabaseAccessor
+ with _$DailyPlanDaoMixin {
+ DailyPlanDao(super.attachedDatabase);
+
+ /// Watch all tasks joined with room name, sorted by nextDueDate ascending.
+ Stream> watchAllTasksWithRoomName() {
+ final query = select(tasks).join([
+ innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
+ ]);
+ query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);
+
+ 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();
+ });
+ }
+
+ /// Count completions recorded today (for progress tracking).
+ /// Counts tasks completed today regardless of their current due date.
+ Stream watchCompletionsToday({DateTime? now}) {
+ final today = now ?? DateTime.now();
+ final startOfDay = DateTime(today.year, today.month, today.day);
+ final endOfDay = startOfDay.add(const Duration(days: 1));
+
+ final query = selectOnly(taskCompletions)
+ ..addColumns([taskCompletions.id.count()])
+ ..where(taskCompletions.completedAt.isBiggerOrEqualValue(startOfDay) &
+ taskCompletions.completedAt.isSmallerThanValue(endOfDay));
+
+ return query.watchSingle().map((row) {
+ return row.read(taskCompletions.id.count()) ?? 0;
+ });
+ }
+}
+```
+
+### Pattern 2: Provider-Layer Date Categorization
+**What:** A Riverpod provider that watches the raw task stream and categorizes tasks into overdue, today, and tomorrow sections. Also computes progress stats.
+**When to use:** Transforming flat task data into the three-section daily plan structure.
+**Example:**
+```dart
+/// Daily plan data categorized into sections.
+class DailyPlanState {
+ final List overdueTasks;
+ final List todayTasks;
+ final List tomorrowTasks;
+ final int completedTodayCount;
+ final int totalTodayCount; // overdue + today (actionable tasks)
+
+ const DailyPlanState({
+ required this.overdueTasks,
+ required this.todayTasks,
+ required this.tomorrowTasks,
+ required this.completedTodayCount,
+ required this.totalTodayCount,
+ });
+}
+
+// Manual StreamProvider (same pattern as tasksInRoomProvider)
+// due to drift Task type issue with riverpod_generator
+final dailyPlanProvider =
+ StreamProvider.autoDispose((ref) {
+ final db = ref.watch(appDatabaseProvider);
+ final taskStream = db.dailyPlanDao.watchAllTasksWithRoomName();
+ final completionStream = db.dailyPlanDao.watchCompletionsToday();
+
+ // Combine both streams using Dart's asyncMap pattern
+ return taskStream.asyncMap((allTasks) async {
+ // Get today's completion count (latest value)
+ final completedToday = await db.dailyPlanDao
+ .watchCompletionsToday().first;
+
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final tomorrow = today.add(const Duration(days: 1));
+ final dayAfterTomorrow = tomorrow.add(const Duration(days: 1));
+
+ final overdue = [];
+ final todayList = [];
+ final tomorrowList = [];
+
+ for (final tw in allTasks) {
+ final dueDate = DateTime(
+ tw.task.nextDueDate.year,
+ tw.task.nextDueDate.month,
+ tw.task.nextDueDate.day,
+ );
+ if (dueDate.isBefore(today)) {
+ overdue.add(tw);
+ } else if (dueDate.isBefore(tomorrow)) {
+ todayList.add(tw);
+ } else if (dueDate.isBefore(dayAfterTomorrow)) {
+ tomorrowList.add(tw);
+ }
+ }
+
+ return DailyPlanState(
+ overdueTasks: overdue, // already sorted by dueDate from query
+ todayTasks: todayList,
+ tomorrowTasks: tomorrowList,
+ completedTodayCount: completedToday,
+ totalTodayCount: overdue.length + todayList.length + completedToday,
+ );
+ });
+});
+```
+
+### Pattern 3: AnimatedList for Task Completion Slide-Out
+**What:** Use `AnimatedList` with `GlobalKey` to animate task removal when checkbox is tapped. The removed item slides horizontally and collapses vertically.
+**When to use:** Overdue and Today sections where tasks have checkboxes.
+**Example:**
+```dart
+// Slide-out animation for completed tasks
+void _onTaskCompleted(int index, TaskWithRoom taskWithRoom) {
+ // 1. Trigger database completion (fire-and-forget)
+ ref.read(taskActionsProvider.notifier).completeTask(taskWithRoom.task.id);
+
+ // 2. Animate the item out of the list
+ _listKey.currentState?.removeItem(
+ index,
+ (context, animation) {
+ // Combine slide + size collapse for smooth exit
+ return SizeTransition(
+ sizeFactor: animation,
+ child: SlideTransition(
+ position: Tween(
+ begin: const Offset(1.0, 0.0), // slide right
+ end: Offset.zero,
+ ).animate(CurvedAnimation(
+ parent: animation,
+ curve: Curves.easeInOut,
+ )),
+ child: DailyPlanTaskRow(
+ taskWithRoom: taskWithRoom,
+ showCheckbox: true,
+ onCompleted: () {},
+ ),
+ ),
+ );
+ },
+ duration: const Duration(milliseconds: 300),
+ );
+}
+```
+
+### Pattern 4: ExpansionTile for Collapsible Demnachst Section
+**What:** Use Flutter's built-in `ExpansionTile` for the "Demnachst (N)" collapsible section. Starts collapsed per user decision.
+**When to use:** Tomorrow tasks section that is read-only and collapsed by default.
+**Example:**
+```dart
+ExpansionTile(
+ initiallyExpanded: false,
+ title: Text(
+ '${l10n.dailyPlanUpcoming} (${tomorrowTasks.length})',
+ style: theme.textTheme.titleMedium,
+ ),
+ children: tomorrowTasks.map((tw) => DailyPlanTaskRow(
+ taskWithRoom: tw,
+ showCheckbox: false, // read-only, no completion from daily plan
+ onRoomTap: () => context.go('/rooms/${tw.roomId}'),
+ )).toList(),
+);
+```
+
+### Pattern 5: Progress Card with LinearProgressIndicator
+**What:** A card/banner at the top of the daily plan showing "X von Y erledigt" with a linear progress bar beneath.
+**When to use:** First widget in the daily plan scroll, shows today's completion progress.
+**Example:**
+```dart
+class ProgressCard extends StatelessWidget {
+ final int completed;
+ final int total;
+
+ const ProgressCard({
+ super.key,
+ required this.completed,
+ required this.total,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final l10n = AppLocalizations.of(context);
+ final progress = total > 0 ? completed / total : 0.0;
+
+ return Card(
+ margin: const EdgeInsets.all(16),
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ l10n.dailyPlanProgress(completed, total),
+ style: theme.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 12),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(4),
+ child: LinearProgressIndicator(
+ value: progress,
+ minHeight: 8,
+ backgroundColor: theme.colorScheme.surfaceContainerHighest,
+ color: theme.colorScheme.primary,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+```
+
+### Anti-Patterns to Avoid
+- **Using `AnimatedList` for ALL sections (including Demnachst):** The tomorrow section is read-only -- no items are added or removed dynamically. A plain `Column` inside `ExpansionTile` is simpler and avoids unnecessary `GlobalKey` management.
+- **Rebuilding `AnimatedList` on every stream emission:** `AnimatedList` requires imperative `insertItem`/`removeItem` calls. Rebuilding the widget discards animation state. The list must synchronize imperatively with stream data, OR use a simpler approach where completion removes from a local list and the stream handles the rest after animation completes.
+- **Using a single monolithic `AnimatedList` for all three sections:** Each section has different behavior (overdue: checkbox, today: checkbox, tomorrow: no checkbox, collapsible). Use separate widgets per section.
+- **Computing progress from stream-only data:** After completing a task, it moves to a future due date and disappears from "today". The completion count must come from `TaskCompletions` table (tasks completed today), not from the absence of tasks in the stream.
+- **Navigating on task row tap:** Per user decision, daily plan task rows have NO row-tap navigation. Only the checkbox and room name tag are interactive. Do NOT reuse `TaskRow` directly -- it has `onTap` navigating to edit form.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Expand/collapse section | Custom `AnimatedContainer` + boolean state | `ExpansionTile` | Material 3 native, handles animation, arrow icon, state persistence automatically |
+| Cross-room task query | Multiple stream providers + in-memory merge | Drift `join` query with `.watch()` | Single SQLite query is more efficient and produces one reactive stream |
+| Progress bar | Custom `CustomPainter` circular indicator | `LinearProgressIndicator` with `value` | Built-in Material 3 widget, themed automatically, supports determinate mode |
+| Slide-out animation | Manual `AnimationController` per row | `AnimatedList.removeItem()` with `SizeTransition` + `SlideTransition` | Framework handles list index bookkeeping and animation lifecycle |
+| Date categorization | Separate DB queries per category | Single query + in-memory partitioning | One Drift stream for all tasks, partitioned in Dart. Fewer database watchers, simpler invalidation |
+
+**Key insight:** Phase 3 is primarily a presentation-layer phase. The data layer changes are minimal (one new DAO method + registering the DAO). Most complexity is in the UI: synchronizing `AnimatedList` state with reactive stream data, and building a polished sectioned scroll view with proper animation.
+
+## Common Pitfalls
+
+### Pitfall 1: AnimatedList State Desynchronization
+**What goes wrong:** `AnimatedList` requires imperative `insertItem()`/`removeItem()` calls to stay in sync with the data. If the widget rebuilds from a new stream emission while an animation is in progress, the list state and data diverge, causing index-out-of-bounds or duplicate item errors.
+**Why it happens:** `AnimatedList` maintains its own internal item count. Stream emissions update the data list independently. If a completion triggers both an `AnimatedList.removeItem()` call AND a stream re-emission (because Drift sees the task's `nextDueDate` changed), the item gets "removed" twice.
+**How to avoid:** Use one of two approaches: (A) Optimistic local list: maintain a local `List` that is initialized from the stream but modified locally on completion. Only re-sync from the stream when a new emission arrives that differs from the local state. (B) Simpler approach: skip `AnimatedList` entirely and use `AnimatedSwitcher` per item with `SizeTransition`, or use `AnimatedList` only for the removal animation then let the stream rebuild the list normally after animation completes. Approach (B) is simpler -- animate out, then after the animation duration, the stream naturally excludes the completed task.
+**Warning signs:** "RangeError: index out of range" or items flickering during completion animation.
+
+### Pitfall 2: Progress Count Accuracy After Completion
+**What goes wrong:** Counting "completed today" by subtracting current due tasks from an initial count loses accuracy. A task completed today moves its `nextDueDate` to a future date, so it disappears from both overdue and today. Without tracking completions separately, the progress denominator shrinks and the bar appears to jump backward.
+**Why it happens:** The total tasks due today changes as tasks are completed (they move to future dates). If you compute `total = overdue.length + today.length`, the total decreases with each completion, making progress misleading (e.g., 0/5 -> complete one -> 0/4 instead of 1/5).
+**How to avoid:** Track `completedTodayCount` from the `TaskCompletions` table (count of completions where `completedAt` is today). Compute `totalToday = remainingOverdue + remainingToday + completedTodayCount`. This way, as tasks are completed, `completedTodayCount` increases and `remaining` decreases, keeping the total stable.
+**Warning signs:** Progress bar shows "0 von 3 erledigt", complete one task, shows "0 von 2 erledigt" instead of "1 von 3 erledigt".
+
+### Pitfall 3: Drift Stream Over-Emission on Cross-Table Join
+**What goes wrong:** A join query watching both `tasks` and `rooms` tables re-fires whenever ANY write happens to either table -- not just relevant rows. Room reordering, for example, triggers a daily plan re-query even though no task data changed.
+**Why it happens:** Drift's stream invalidation is table-level, not row-level.
+**How to avoid:** This is generally acceptable at household-app scale (dozens of tasks, not thousands). If needed, use `ref.select()` in the widget to avoid rebuilding when the data hasn't meaningfully changed. Alternatively, `distinctUntilChanged` on the stream (using `ListEquality` from `collection` package) prevents redundant widget rebuilds.
+**Warning signs:** Daily plan screen rebuilds when user reorders rooms on the Rooms tab.
+
+### Pitfall 4: Empty State vs Loading State Confusion
+**What goes wrong:** Showing the "all clear" empty state while data is still loading gives users a false impression that nothing is due.
+**Why it happens:** `AsyncValue.when()` with `data: []` is indistinguishable from "no tasks at all" vs "tasks haven't loaded yet" if not handled carefully.
+**How to avoid:** Always handle `loading`, `error`, and `data` states in `asyncValue.when()`. Show a subtle progress indicator during loading. Only show the "all clear" empty state when `data` is loaded AND both overdue and today lists are empty.
+**Warning signs:** App briefly flashes "Alles erledigt!" on startup before tasks load.
+
+### Pitfall 5: Room Name Tag Navigation Conflict with Tab Shell
+**What goes wrong:** Tapping the room name tag on a daily plan task should navigate to that room's task list, but `context.go('/rooms/$roomId')` is on a different tab branch. The navigation switches tabs, which may lose scroll position on the Home tab.
+**Why it happens:** GoRouter's `StatefulShellRoute.indexedStack` preserves tab state, but `context.go('/rooms/$roomId')` navigates within the Rooms branch, switching the active tab.
+**How to avoid:** This is actually the desired behavior -- the user explicitly tapped the room tag to navigate there. The Home tab state is preserved by `indexedStack` and will be restored when the user taps back to the Home tab. No special handling needed beyond using `context.go('/rooms/$roomId')`.
+**Warning signs:** None expected -- this is standard GoRouter tab behavior.
+
+## Code Examples
+
+### Complete DailyPlanDao with Join Query
+```dart
+// Source: drift.simonbinder.eu/dart_api/select/ (joins documentation)
+import 'package:drift/drift.dart';
+import '../../../core/database/database.dart';
+
+part 'daily_plan_dao.g.dart';
+
+/// A task paired with its room for daily plan display.
+class TaskWithRoom {
+ final Task task;
+ final String roomName;
+ final int roomId;
+
+ const TaskWithRoom({
+ required this.task,
+ required this.roomName,
+ required this.roomId,
+ });
+}
+
+@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])
+class DailyPlanDao extends DatabaseAccessor
+ with _$DailyPlanDaoMixin {
+ DailyPlanDao(super.attachedDatabase);
+
+ /// Watch all tasks joined with room name, sorted by nextDueDate ascending.
+ /// Includes ALL tasks (overdue, today, future) -- filtering is done in the
+ /// provider layer to avoid multiple queries.
+ Stream> watchAllTasksWithRoomName() {
+ final query = select(tasks).join([
+ innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
+ ]);
+ query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);
+
+ 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();
+ });
+ }
+
+ /// Count task completions recorded today.
+ Stream watchCompletionsToday({DateTime? today}) {
+ final now = today ?? DateTime.now();
+ final startOfDay = DateTime(now.year, now.month, now.day);
+ final endOfDay = startOfDay.add(const Duration(days: 1));
+
+ return customSelect(
+ 'SELECT COUNT(*) AS c FROM task_completions '
+ 'WHERE completed_at >= ? AND completed_at < ?',
+ variables: [Variable(startOfDay), Variable(endOfDay)],
+ readsFrom: {taskCompletions},
+ ).watchSingle().map((row) => row.read('c'));
+ }
+}
+```
+
+### Daily Plan Task Row (Adapted from TaskRow)
+```dart
+// Source: existing TaskRow pattern adapted per CONTEXT.md decisions
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+
+/// Warm coral/terracotta color for overdue styling (reused from TaskRow).
+const _overdueColor = Color(0xFFE07A5F);
+
+class DailyPlanTaskRow extends ConsumerWidget {
+ const DailyPlanTaskRow({
+ super.key,
+ required this.taskWithRoom,
+ required this.showCheckbox,
+ this.onCompleted,
+ });
+
+ final TaskWithRoom taskWithRoom;
+ final bool showCheckbox; // false for tomorrow (read-only)
+ final VoidCallback? onCompleted;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final theme = Theme.of(context);
+ final task = taskWithRoom.task;
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final dueDate = DateTime(
+ task.nextDueDate.year,
+ task.nextDueDate.month,
+ task.nextDueDate.day,
+ );
+ final isOverdue = dueDate.isBefore(today);
+ final relativeDateText = formatRelativeDate(task.nextDueDate, now);
+
+ return ListTile(
+ leading: showCheckbox
+ ? Checkbox(
+ value: false,
+ onChanged: (_) => onCompleted?.call(),
+ )
+ : null,
+ title: Text(
+ task.name,
+ style: theme.textTheme.titleMedium,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ subtitle: Row(
+ children: [
+ // Room name tag (tappable)
+ GestureDetector(
+ onTap: () => context.go('/rooms/${taskWithRoom.roomId}'),
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
+ decoration: BoxDecoration(
+ color: theme.colorScheme.secondaryContainer,
+ borderRadius: BorderRadius.circular(4),
+ ),
+ child: Text(
+ taskWithRoom.roomName,
+ style: theme.textTheme.labelSmall?.copyWith(
+ color: theme.colorScheme.onSecondaryContainer,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(width: 8),
+ Text(
+ relativeDateText,
+ style: theme.textTheme.bodySmall?.copyWith(
+ color: isOverdue
+ ? _overdueColor
+ : theme.colorScheme.onSurfaceVariant,
+ ),
+ ),
+ ],
+ ),
+ // NO onTap -- daily plan is a focused "get things done" screen
+ );
+ }
+}
+```
+
+### "All Clear" Empty State
+```dart
+// Source: established empty state pattern from HomeScreen and TaskListScreen
+Widget _buildAllClearState(BuildContext context) {
+ final theme = Theme.of(context);
+ final l10n = AppLocalizations.of(context);
+
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.celebration_outlined,
+ size: 80,
+ color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
+ ),
+ const SizedBox(height: 24),
+ Text(
+ l10n.dailyPlanAllClearTitle,
+ style: theme.textTheme.headlineSmall,
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 8),
+ Text(
+ l10n.dailyPlanAllClearMessage,
+ style: theme.textTheme.bodyLarge?.copyWith(
+ color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ],
+ ),
+ ),
+ );
+}
+```
+
+### New Localization Keys (app_de.arb additions)
+```json
+{
+ "dailyPlanProgress": "{completed} von {total} erledigt",
+ "@dailyPlanProgress": {
+ "placeholders": {
+ "completed": { "type": "int" },
+ "total": { "type": "int" }
+ }
+ },
+ "dailyPlanSectionOverdue": "Ueberfaellig",
+ "dailyPlanSectionToday": "Heute",
+ "dailyPlanSectionUpcoming": "Demnachst",
+ "dailyPlanUpcomingCount": "Demnachst ({count})",
+ "@dailyPlanUpcomingCount": {
+ "placeholders": {
+ "count": { "type": "int" }
+ }
+ },
+ "dailyPlanAllClearTitle": "Alles erledigt!",
+ "dailyPlanAllClearMessage": "Keine Aufgaben fuer heute. Geniesse den Moment!"
+}
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| Multiple separate DB queries for overdue/today/tomorrow | Single join query with in-memory partitioning | Best practice with Drift 2.x | Fewer database watchers, one stream invalidation path |
+| `rxdart` `CombineLatest` for merging streams | `ref.watch()` on multiple providers in Riverpod 3 | Riverpod 3.0 (Sep 2025) | `ref.watch(provider.stream)` removed; use computed providers instead |
+| `ExpansionTileController` | `ExpansibleController` (Flutter 3.32+) | Flutter 3.32 | `ExpansionTileController` deprecated in favor of `ExpansibleController` |
+| `LinearProgressIndicator` 2023 design | `year2023: false` for 2024 design spec | Flutter 3.41+ | New design with rounded corners, gap between tracks. Still defaulting to 2023 design unless opted in. |
+
+**Deprecated/outdated:**
+- `ExpansionTileController`: Deprecated after Flutter 3.31. Use `ExpansibleController` for programmatic expand/collapse. However, `ExpansionTile` still works with `initiallyExpanded` without needing a controller.
+- `ref.watch(provider.stream)`: Removed in Riverpod 3. Cannot access underlying stream directly. Use `ref.watch` on the provider value instead.
+
+## Open Questions
+
+1. **AnimatedList vs simpler approach for completion animation**
+ - What we know: `AnimatedList` provides proper `removeItem()` with animation, but requires imperative state management that can desync with Drift streams.
+ - What's unclear: Whether the complexity of `AnimatedList` + stream synchronization is worth it vs a simpler approach (e.g., `AnimatedSwitcher` wrapping each item, or just letting items disappear on stream re-emission).
+ - Recommendation: Use `AnimatedList` for overdue + today sections. On completion, call `removeItem()` for the slide-out animation, then let the stream naturally update. The stream re-emission after the animation completes will be a no-op if the item is already gone. Use a local copy of the list to avoid desync -- only update from stream when the local list is stale. This is manageable because the daily plan list is typically small (< 30 items).
+
+2. **Progress count accuracy edge case: midnight rollover**
+ - What we know: "Today" is `DateTime.now()` at query time. If the app stays open past midnight, "today" shifts.
+ - What's unclear: Whether the daily plan should auto-refresh at midnight or require app restart.
+ - Recommendation: Not critical for v1. The stream re-fires on any DB write. The user will see stale "today" data only if they leave the app open overnight without interacting. Acceptable for a household app.
+
+## Validation Architecture
+
+### Test Framework
+| Property | Value |
+|----------|-------|
+| Framework | flutter_test (built-in) |
+| Config file | none -- standard Flutter test setup |
+| Quick run command | `flutter test test/features/home/` |
+| Full suite command | `flutter test` |
+
+### Phase Requirements -> Test Map
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| PLAN-01 | Cross-room query returns tasks with room names | unit | `flutter test test/features/home/data/daily_plan_dao_test.dart` | Wave 0 |
+| PLAN-02 | Tasks with nextDueDate before today categorized as overdue | unit | `flutter test test/features/home/data/daily_plan_dao_test.dart` | Wave 0 |
+| PLAN-03 | Tasks due tomorrow returned in upcoming list | unit | `flutter test test/features/home/data/daily_plan_dao_test.dart` | Wave 0 |
+| PLAN-04 | Completing a task via DAO records completion and updates due date | unit | Already covered by `test/features/tasks/data/tasks_dao_test.dart` | Exists |
+| PLAN-05 | Completions today count matches actual completions | unit | `flutter test test/features/home/data/daily_plan_dao_test.dart` | Wave 0 |
+| PLAN-06 | Empty state shown when no overdue/today tasks exist | widget | `flutter test test/features/home/presentation/home_screen_test.dart` | Wave 0 |
+| CLEAN-01 | Room card shows cleanliness indicator | unit | Already covered by `test/features/rooms/data/rooms_dao_test.dart` | Exists |
+
+### Sampling Rate
+- **Per task commit:** `flutter test test/features/home/`
+- **Per wave merge:** `flutter test`
+- **Phase gate:** Full suite green before `/gsd:verify-work`
+
+### Wave 0 Gaps
+- [ ] `test/features/home/data/daily_plan_dao_test.dart` -- covers PLAN-01, PLAN-02, PLAN-03, PLAN-05 (cross-room join query, date categorization, completion count)
+- [ ] `test/features/home/presentation/home_screen_test.dart` -- covers PLAN-06 (empty state rendering) and basic section rendering
+- [ ] No new framework dependencies needed; existing `flutter_test` + `drift` `NativeDatabase.memory()` pattern is sufficient
+
+## Sources
+
+### Primary (HIGH confidence)
+- [Drift Select/Join docs](https://drift.simonbinder.eu/dart_api/select/) - Join query syntax, `readTable()`, `readTableOrNull()`, ordering on joins
+- [Drift Stream docs](https://drift.simonbinder.eu/dart_api/streams/) - `.watch()` mechanism, table-level invalidation, stream behavior
+- [Flutter AnimatedList API](https://api.flutter.dev/flutter/widgets/AnimatedList-class.html) - `removeItem()`, `GlobalKey`, animation builder
+- [Flutter AnimatedListState.removeItem](https://api.flutter.dev/flutter/widgets/AnimatedListState/removeItem.html) - Method signature, duration, builder pattern
+- [Flutter ExpansionTile API](https://api.flutter.dev/flutter/material/ExpansionTile-class.html) - `initiallyExpanded`, Material 3 theming, `controlAffinity`
+- [Flutter LinearProgressIndicator API](https://api.flutter.dev/flutter/material/LinearProgressIndicator-class.html) - Determinate mode with `value`, `minHeight`, Material 3 styling
+- [Flutter M3 Progress Indicators Breaking Changes](https://docs.flutter.dev/release/breaking-changes/updated-material-3-progress-indicators) - `year2023` flag, new 2024 design spec
+- Existing project codebase: `TasksDao`, `TaskRow`, `HomeScreen`, `RoomsDao`, `room_providers.dart`, `task_providers.dart`, `router.dart`
+
+### Secondary (MEDIUM confidence)
+- [Expansible in Flutter 3.32](https://himanshu-agarwal.medium.com/expansible-in-flutter-3-32-why-it-matters-how-to-use-it-727eeacb8dd2) - `ExpansibleController` deprecating `ExpansionTileController`
+- [Riverpod combining providers](https://app.studyraid.com/en/read/12027/384445/combining-multiple-providers-for-complex-state-management) - `ref.watch` pattern for computed providers
+- [Riverpod 3 stream alternatives](https://yfujiki.medium.com/an-alternative-of-stream-operation-in-riverpod-3-627a45f65140) - Stream combining without `.stream` access
+
+### Tertiary (LOW confidence)
+- None -- all findings verified with official docs or established project patterns
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH - All libraries already in project, no new dependencies
+- Architecture: HIGH - Patterns directly extend Phase 2 established conventions (DAO, StreamProvider, widget patterns), verified against existing code
+- Data layer (join query): HIGH - Drift join documentation is clear and well-tested; project already uses Drift 2.31.0 with the exact same pattern available
+- Animation (AnimatedList): MEDIUM - AnimatedList is well-documented but synchronization with reactive streams requires careful implementation. The desync pitfall is real but manageable at household-app scale.
+- Pitfalls: HIGH - All identified pitfalls are based on established Flutter/Drift behavior verified in official docs
+
+**Research date:** 2026-03-16
+**Valid until:** 2026-04-16 (stable stack, no fast-moving dependencies)