--- phase: 06-task-history plan: 01 type: execute wave: 1 depends_on: [] files_modified: - lib/features/tasks/data/tasks_dao.dart - lib/features/tasks/data/tasks_dao.g.dart - lib/features/tasks/presentation/task_history_sheet.dart - lib/features/tasks/presentation/task_form_screen.dart - lib/features/home/presentation/calendar_task_row.dart - lib/l10n/app_de.arb - lib/l10n/app_localizations.dart - lib/l10n/app_localizations_de.dart - test/features/tasks/data/task_history_dao_test.dart autonomous: true requirements: [HIST-01, HIST-02] must_haves: truths: - "Every task completion is recorded with a timestamp and persists across app restarts" - "User can open a history view from the task edit form showing all past completion dates in reverse-chronological order" - "History view shows a meaningful empty state if the task has never been completed" artifacts: - path: "lib/features/tasks/data/tasks_dao.dart" provides: "watchCompletionsForTask(int taskId) stream method" contains: "watchCompletionsForTask" - path: "lib/features/tasks/presentation/task_history_sheet.dart" provides: "Bottom sheet displaying task completion history" exports: ["showTaskHistorySheet"] - path: "lib/features/tasks/presentation/task_form_screen.dart" provides: "Verlauf button in edit mode opening history sheet" contains: "showTaskHistorySheet" - path: "lib/features/home/presentation/calendar_task_row.dart" provides: "onTap navigation to task edit form" contains: "context.go" - path: "test/features/tasks/data/task_history_dao_test.dart" provides: "Tests for completion history DAO query" min_lines: 30 key_links: - from: "lib/features/tasks/presentation/task_form_screen.dart" to: "lib/features/tasks/presentation/task_history_sheet.dart" via: "showTaskHistorySheet call in Verlauf button onTap" pattern: "showTaskHistorySheet" - from: "lib/features/tasks/presentation/task_history_sheet.dart" to: "lib/features/tasks/data/tasks_dao.dart" via: "watchCompletionsForTask stream consumption" pattern: "watchCompletionsForTask" - from: "lib/features/home/presentation/calendar_task_row.dart" to: "TaskFormScreen" via: "GoRouter navigation on row tap" pattern: "context\\.go.*tasks" --- Add task completion history: a DAO query to fetch completions, a bottom sheet to display them, integration into the task edit form, and CalendarTaskRow onTap navigation. Purpose: Users can see exactly when each task was completed in the past, building trust that the scheduling loop is working correctly. Output: Working history view accessible from task edit form, completion data surfaced from existing TaskCompletions table. @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-task-history/06-CONTEXT.md From lib/core/database/database.dart: ```dart /// TaskCompletions table: records when a task was completed. class TaskCompletions extends Table { IntColumn get id => integer().autoIncrement()(); IntColumn get taskId => integer().references(Tasks, #id)(); DateTimeColumn get completedAt => dateTime()(); } @DriftDatabase( tables: [Rooms, Tasks, TaskCompletions], daos: [RoomsDao, TasksDao, DailyPlanDao, CalendarDao], ) class AppDatabase extends _$AppDatabase { ... } ``` From lib/features/tasks/data/tasks_dao.dart: ```dart @DriftAccessor(tables: [Tasks, TaskCompletions]) class TasksDao extends DatabaseAccessor with _$TasksDaoMixin { TasksDao(super.attachedDatabase); Stream> watchTasksInRoom(int roomId) { ... } Future insertTask(TasksCompanion task) => into(tasks).insert(task); Future updateTask(Task task) => update(tasks).replace(task); Future deleteTask(int taskId) { ... } Future completeTask(int taskId, {DateTime? now}) { ... } Future getOverdueTaskCount(int roomId, {DateTime? today}) { ... } } ``` From lib/features/tasks/presentation/task_form_screen.dart: ```dart class TaskFormScreen extends ConsumerStatefulWidget { final int? roomId; final int? taskId; const TaskFormScreen({super.key, this.roomId, this.taskId}); bool get isEditing => taskId != null; } // build() returns Scaffold with AppBar + Form > ListView with fields // In edit mode: _existingTask is loaded via _loadExistingTask() ``` 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, }); final TaskWithRoom taskWithRoom; final VoidCallback onCompleted; final bool isOverdue; } // TaskWithRoom has: task (Task), roomName (String), roomId (int) ``` 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}); } ``` Bottom sheet pattern from lib/features/rooms/presentation/icon_picker_sheet.dart: ```dart Future showIconPickerSheet({ required BuildContext context, String? selectedIconName, }) { return showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => IconPickerSheet(...), ); } // Sheet uses SafeArea > Padding > Column(mainAxisSize: MainAxisSize.min) with drag handle ``` Router pattern from lib/core/router/router.dart: ```dart // Task edit route: /rooms/:roomId/tasks/:taskId GoRoute( path: 'tasks/:taskId', builder: (context, state) { final taskId = int.parse(state.pathParameters['taskId']!); return TaskFormScreen(taskId: taskId); }, ), ``` Task 1: Add DAO query, provider, localization, and tests for completion history lib/features/tasks/data/tasks_dao.dart, lib/features/tasks/data/tasks_dao.g.dart, lib/l10n/app_de.arb, lib/l10n/app_localizations.dart, lib/l10n/app_localizations_de.dart, test/features/tasks/data/task_history_dao_test.dart - watchCompletionsForTask(taskId) returns Stream of TaskCompletion list ordered by completedAt DESC (newest first) - Empty list returned when no completions exist for a given taskId - After completeTask(taskId) is called, watchCompletionsForTask(taskId) emits a list containing the new completion with correct timestamp - Completions for different tasks are isolated (taskId=1 completions do not appear in taskId=2 stream) - Multiple completions for the same task are all returned in reverse-chronological order RED phase: Create test/features/tasks/data/task_history_dao_test.dart with tests for the behaviors above. Use the existing in-memory database test pattern: AppDatabase(NativeDatabase.memory()), get TasksDao, insert a room and tasks, then test. Run tests -- they MUST fail (watchCompletionsForTask does not exist yet). GREEN phase: 1. In lib/features/tasks/data/tasks_dao.dart, add: ```dart /// Watch all completions for a task, newest first. Stream> watchCompletionsForTask(int taskId) { return (select(taskCompletions) ..where((c) => c.taskId.equals(taskId)) ..orderBy([(c) => OrderingTerm.desc(c.completedAt)])) .watch(); } ``` 2. Run `dart run build_runner build --delete-conflicting-outputs` to regenerate tasks_dao.g.dart. 3. Run tests -- they MUST pass. Then add localization strings to lib/l10n/app_de.arb: - "taskHistoryTitle": "Verlauf" - "taskHistoryEmpty": "Noch nie erledigt" - "taskHistoryCount": "{count} Mal erledigt" with @taskHistoryCount placeholder for count (int) Run `flutter gen-l10n` to regenerate app_localizations.dart and app_localizations_de.dart. NOTE: No separate Riverpod provider is needed -- the bottom sheet will access the DAO directly via appDatabaseProvider (same pattern as _loadExistingTask in TaskFormScreen). This keeps it simple since the sheet is a one-shot modal, not a long-lived screen. cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/data/task_history_dao_test.dart -r expanded && flutter analyze --no-fatal-infos watchCompletionsForTask method exists on TasksDao, returns Stream of completions sorted newest-first. All new DAO tests pass. All 101+ existing tests still pass. Three German localization strings (taskHistoryTitle, taskHistoryEmpty, taskHistoryCount) are available via AppLocalizations. Task 2: Build history bottom sheet, wire into TaskFormScreen, add CalendarTaskRow navigation lib/features/tasks/presentation/task_history_sheet.dart, lib/features/tasks/presentation/task_form_screen.dart, lib/features/home/presentation/calendar_task_row.dart 1. Create lib/features/tasks/presentation/task_history_sheet.dart: - Export a top-level function: `Future<void> showTaskHistorySheet({required BuildContext context, required int taskId})` - Uses `showModalBottomSheet` with `isScrollControlled: true` following icon_picker_sheet.dart pattern - The sheet widget is a ConsumerWidget (needs ref to access DAO) - Uses `ref.read(appDatabaseProvider).tasksDao.watchCompletionsForTask(taskId)` wrapped in a StreamBuilder - Layout: SafeArea > Padding(16) > Column(mainAxisSize: min): a. Drag handle (same as icon_picker_sheet: Container 32x4, onSurfaceVariant 0.4 alpha, rounded) b. Title: AppLocalizations.of(context).taskHistoryTitle (i.e. "Verlauf"), titleMedium style c. Optional: completion count summary below title using taskHistoryCount string -- show only when count > 0 d. SizedBox(height: 16) e. StreamBuilder on watchCompletionsForTask: - Loading: Center(CircularProgressIndicator()) - Empty data: centered Column with Icon(Icons.history, size: 48, color: onSurfaceVariant) + SizedBox(8) + Text(taskHistoryEmpty), style: bodyLarge, color: onSurfaceVariant - Has data: ConstrainedBox(maxHeight: MediaQuery.of(context).size.height * 0.4) > ListView.builder: Each item: ListTile with leading Icon(Icons.check_circle_outline, color: primary), title: DateFormat('dd.MM.yyyy', 'de').format(completion.completedAt), subtitle: DateFormat('HH:mm', 'de').format(completion.completedAt) f. SizedBox(height: 8) at bottom 2. Modify lib/features/tasks/presentation/task_form_screen.dart: - Import task_history_sheet.dart - In the build() method's ListView children, AFTER the due date picker section and ONLY when `widget.isEditing` is true, add: ``` const SizedBox(height: 24), const Divider(), ListTile( leading: const Icon(Icons.history), title: Text(l10n.taskHistoryTitle), trailing: const Icon(Icons.chevron_right), onTap: () => showTaskHistorySheet(context: context, taskId: widget.taskId!), ), ``` - This adds a "Verlauf" row that opens the history bottom sheet 3. Modify lib/features/home/presentation/calendar_task_row.dart: - Add an onTap callback to the ListTile that navigates to the task edit form - The CalendarTaskRow already has access to taskWithRoom.task.id and taskWithRoom.roomId - Add to ListTile: `onTap: () => context.go('/rooms/${taskWithRoom.roomId}/tasks/${taskWithRoom.task.id}')` - This enables: CalendarTaskRow tap -> TaskFormScreen (edit mode) -> "Verlauf" button -> history sheet - Keep the existing onCompleted checkbox behavior unchanged cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test && flutter analyze --no-fatal-infos History bottom sheet opens from TaskFormScreen in edit mode via "Verlauf" row. Sheet shows completion dates in dd.MM.yyyy + HH:mm format, reverse-chronological. Empty state shows Icons.history + "Noch nie erledigt" message. CalendarTaskRow tapping navigates to TaskFormScreen for that task. All existing tests still pass. dart analyze clean. Phase 6 verification checks: 1. `flutter test` -- all tests pass (101 existing + new DAO tests) 2. `flutter analyze --no-fatal-infos` -- zero issues 3. Manual flow: Open app > tap a task in calendar > task edit form opens > "Verlauf" row visible > tap it > bottom sheet shows history or empty state 4. Manual flow: Complete a task via checkbox > navigate to that task's edit form > tap "Verlauf" > new completion entry appears with timestamp - HIST-01: Task completion recording verified via DAO tests (completions already written by completeTask; new query surfaces them) - HIST-02: History bottom sheet accessible from task edit form, shows all past completions reverse-chronologically with German date/time formatting, shows meaningful empty state - CalendarTaskRow tapping navigates to task edit form (history one tap away) - Zero regressions: all existing tests pass, dart analyze clean After completion, create `.planning/phases/06-task-history/06-01-SUMMARY.md`