From 03ebaac5a899c41b574f7276c3baefda0a369de6 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 16 Mar 2026 21:50:11 +0100 Subject: [PATCH] docs(06-task-history): create phase plan --- .planning/ROADMAP.md | 6 +- .../phases/06-task-history/06-01-PLAN.md | 312 ++++++++++++++++++ 2 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/06-task-history/06-01-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 90f7550..1d8ba41 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -50,7 +50,9 @@ Plans: 1. Every task completion (tap done in any view) is recorded in the database with a precise timestamp — data persists across app restarts 2. From a task's detail or context menu the user can open a history view listing all past completion dates for that task in reverse-chronological order 3. The history view shows a meaningful empty state if the task has never been completed -**Plans**: TBD +**Plans:** 1 plan +Plans: +- [ ] 06-01-PLAN.md — DAO query + history bottom sheet + TaskFormScreen integration + CalendarTaskRow navigation ### Phase 7: Task Sorting **Goal**: Users can reorder task lists by the dimension most useful to them — name, how often the task recurs, or how much effort it requires @@ -72,5 +74,5 @@ Plans: | 3. Daily Plan and Cleanliness | v1.0 | 3/3 | Complete | 2026-03-16 | | 4. Notifications | v1.0 | 3/3 | Complete | 2026-03-16 | | 5. Calendar Strip | 2/2 | Complete | 2026-03-16 | - | -| 6. Task History | v1.1 | 0/? | Not started | - | +| 6. Task History | v1.1 | 0/1 | Planned | - | | 7. Task Sorting | v1.1 | 0/? | Not started | - | diff --git a/.planning/phases/06-task-history/06-01-PLAN.md b/.planning/phases/06-task-history/06-01-PLAN.md new file mode 100644 index 0000000..6bffe4d --- /dev/null +++ b/.planning/phases/06-task-history/06-01-PLAN.md @@ -0,0 +1,312 @@ +--- +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` +