chore: complete v1.1 milestone
Archive v1.1 Calendar & Polish milestone artifacts (roadmap, requirements, phase directories) to milestones/. Evolve PROJECT.md with validated requirements and new key decisions. Update RETROSPECTIVE.md with v1.1 section and cross-milestone trends. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
312
.planning/milestones/v1.1-phases/06-task-history/06-01-PLAN.md
Normal file
312
.planning/milestones/v1.1-phases/06-task-history/06-01-PLAN.md
Normal file
@@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/jlmak/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/06-task-history/06-CONTEXT.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
||||
<!-- Executor should use these directly -- no codebase exploration needed. -->
|
||||
|
||||
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<AppDatabase> with _$TasksDaoMixin {
|
||||
TasksDao(super.attachedDatabase);
|
||||
|
||||
Stream<List<Task>> watchTasksInRoom(int roomId) { ... }
|
||||
Future<int> insertTask(TasksCompanion task) => into(tasks).insert(task);
|
||||
Future<bool> updateTask(Task task) => update(tasks).replace(task);
|
||||
Future<void> deleteTask(int taskId) { ... }
|
||||
Future<void> completeTask(int taskId, {DateTime? now}) { ... }
|
||||
Future<int> 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<String?> showIconPickerSheet({
|
||||
required BuildContext context,
|
||||
String? selectedIconName,
|
||||
}) {
|
||||
return showModalBottomSheet<String>(
|
||||
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);
|
||||
},
|
||||
),
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Add DAO query, provider, localization, and tests for completion history</name>
|
||||
<files>
|
||||
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
|
||||
</files>
|
||||
<behavior>
|
||||
- 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
|
||||
</behavior>
|
||||
<action>
|
||||
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<List<TaskCompletion>> 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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/data/task_history_dao_test.dart -r expanded && flutter analyze --no-fatal-infos</automated>
|
||||
</verify>
|
||||
<done>
|
||||
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.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Build history bottom sheet, wire into TaskFormScreen, add CalendarTaskRow navigation</name>
|
||||
<files>
|
||||
lib/features/tasks/presentation/task_history_sheet.dart,
|
||||
lib/features/tasks/presentation/task_form_screen.dart,
|
||||
lib/features/home/presentation/calendar_task_row.dart
|
||||
</files>
|
||||
<action>
|
||||
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
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test && flutter analyze --no-fatal-infos</automated>
|
||||
</verify>
|
||||
<done>
|
||||
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.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/06-task-history/06-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
phase: 06-task-history
|
||||
plan: 01
|
||||
subsystem: database, ui
|
||||
tags: [drift, flutter, riverpod, go_router, intl, bottom-sheet, stream]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 05-calendar-strip
|
||||
provides: CalendarTaskRow widget and CalendarDayList that render tasks in the home screen
|
||||
provides:
|
||||
- watchCompletionsForTask(taskId) DAO stream on TasksDao — sorted newest-first
|
||||
- task_history_sheet.dart with showTaskHistorySheet() function
|
||||
- Verlauf ListTile in TaskFormScreen (edit mode) opening history bottom sheet
|
||||
- CalendarTaskRow onTap navigation to TaskFormScreen for the tapped task
|
||||
affects: [07-task-sorting, future-phases-using-TaskFormScreen]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Bottom sheet follows icon_picker_sheet pattern: showModalBottomSheet with isScrollControlled, ConsumerWidget inside, SafeArea > Padding > Column(mainAxisSize.min)"
|
||||
- "StreamBuilder on DAO stream directly accessed via ref.read(appDatabaseProvider).tasksDao.methodName (no separate Riverpod provider for one-shot modals)"
|
||||
- "TDD: RED test commit followed by GREEN implementation commit"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- lib/features/tasks/presentation/task_history_sheet.dart
|
||||
- test/features/tasks/data/task_history_dao_test.dart
|
||||
modified:
|
||||
- lib/features/tasks/data/tasks_dao.dart
|
||||
- lib/features/tasks/data/tasks_dao.g.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
|
||||
|
||||
key-decisions:
|
||||
- "No separate Riverpod provider for history sheet — ref.read(appDatabaseProvider) directly in ConsumerWidget keeps it simple for a one-shot modal"
|
||||
- "CalendarTaskRow onTap routes to /rooms/:roomId/tasks/:taskId so history is always one tap away from the home screen"
|
||||
- "Count summary line shown above list when completions > 0; not shown for empty state"
|
||||
|
||||
patterns-established:
|
||||
- "History sheet: showModalBottomSheet returning Future<void>, ConsumerWidget sheet with StreamBuilder on DAO stream"
|
||||
- "Edit-mode-only ListTile pattern: if (widget.isEditing) [...] in TaskFormScreen ListView children"
|
||||
|
||||
requirements-completed: [HIST-01, HIST-02]
|
||||
|
||||
# Metrics
|
||||
duration: 5min
|
||||
completed: 2026-03-16
|
||||
---
|
||||
|
||||
# Phase 6 Plan 1: Task History Summary
|
||||
|
||||
**Drift DAO stream for task completion history, bottom sheet with reverse-chronological German-formatted dates, wired from CalendarTaskRow tap through TaskFormScreen Verlauf button**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 5 min
|
||||
- **Started:** 2026-03-16T20:52:49Z
|
||||
- **Completed:** 2026-03-16T20:57:19Z
|
||||
- **Tasks:** 2 (Task 1 TDD: RED + GREEN + localization; Task 2: sheet + wiring + navigation)
|
||||
- **Files modified:** 9
|
||||
|
||||
## Accomplishments
|
||||
- `watchCompletionsForTask(int taskId)` added to TasksDao: returns `Stream<List<TaskCompletion>>` sorted by completedAt DESC
|
||||
- Task history bottom sheet (`task_history_sheet.dart`) with StreamBuilder, empty state, German date/time formatting via intl
|
||||
- Verlauf ListTile added to TaskFormScreen edit mode, opens history sheet on tap
|
||||
- CalendarTaskRow gains `onTap` that navigates via GoRouter to the task edit form, making history one tap away from the calendar
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **RED - Failing DAO tests** - `2687f5e` (test)
|
||||
2. **Task 1: DAO method, localization** - `ceae7d7` (feat)
|
||||
3. **Task 2: History sheet, form wiring, navigation** - `9f902ff` (feat)
|
||||
|
||||
**Plan metadata:** (docs commit — see below)
|
||||
|
||||
_Note: TDD tasks have separate RED (test) and GREEN (feat) commits_
|
||||
|
||||
## Files Created/Modified
|
||||
- `lib/features/tasks/data/tasks_dao.dart` - Added watchCompletionsForTask stream method
|
||||
- `lib/features/tasks/data/tasks_dao.g.dart` - Regenerated by build_runner
|
||||
- `lib/features/tasks/presentation/task_history_sheet.dart` - New: bottom sheet with StreamBuilder, empty state, completion list
|
||||
- `lib/features/tasks/presentation/task_form_screen.dart` - Added Verlauf ListTile in edit mode
|
||||
- `lib/features/home/presentation/calendar_task_row.dart` - Added onTap navigation to task edit form
|
||||
- `lib/l10n/app_de.arb` - Added taskHistoryTitle, taskHistoryEmpty, taskHistoryCount strings
|
||||
- `lib/l10n/app_localizations.dart` - Regenerated (abstract class updated)
|
||||
- `lib/l10n/app_localizations_de.dart` - Regenerated (German implementation updated)
|
||||
- `test/features/tasks/data/task_history_dao_test.dart` - New: 5 tests covering empty state, single/multiple completions, task isolation, stream reactivity
|
||||
|
||||
## Decisions Made
|
||||
- No separate Riverpod provider for history sheet: `ref.read(appDatabaseProvider).tasksDao.watchCompletionsForTask(taskId)` directly in the ConsumerWidget. One-shot modals do not need a dedicated provider.
|
||||
- CalendarTaskRow navigation uses `context.go('/rooms/.../tasks/...')` consistent with existing GoRouter route patterns.
|
||||
- Removed unused `import 'package:drift/drift.dart'` from test file (Rule 1 auto-fix during GREEN verification).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] Removed unused import from test file**
|
||||
- **Found during:** Task 1 (GREEN phase, flutter analyze)
|
||||
- **Issue:** `import 'package:drift/drift.dart'` was copied from the existing tasks_dao_test.dart pattern but not needed in the new history test file (no `Value()` usage)
|
||||
- **Fix:** Removed the unused import line
|
||||
- **Files modified:** test/features/tasks/data/task_history_dao_test.dart
|
||||
- **Verification:** flutter analyze reports zero issues
|
||||
- **Committed in:** ceae7d7 (Task 1 feat commit)
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 1 auto-fixed (1 bug — unused import)
|
||||
**Impact on plan:** Trivial cleanup. No scope creep.
|
||||
|
||||
## Issues Encountered
|
||||
None — plan executed smoothly. All 106 tests pass (101 pre-existing + 5 new DAO tests), zero analyze issues.
|
||||
|
||||
## User Setup Required
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
- Phase 6 Plan 1 complete. Task history is fully functional.
|
||||
- Phase 7 (task sorting) can proceed independently.
|
||||
- No blockers.
|
||||
|
||||
---
|
||||
*Phase: 06-task-history*
|
||||
*Completed: 2026-03-16*
|
||||
@@ -0,0 +1,81 @@
|
||||
# Phase 6: Task History - Context
|
||||
|
||||
**Gathered:** 2026-03-16
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## Phase Boundary
|
||||
|
||||
Let users view past completion dates for any individual task. The data layer already records completions (TaskCompletions table + completeTask writes timestamps). This phase adds a DAO query and a UI to surface that data. Requirements: HIST-01 (verify recording works), HIST-02 (view history).
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## Implementation Decisions
|
||||
|
||||
### Entry point
|
||||
- From the task edit form (TaskFormScreen) in edit mode: add a "Verlauf" (History) button/row that opens the history view
|
||||
- From CalendarTaskRow: add onTap to navigate to the task edit form (currently only has checkbox) — history is then one tap away
|
||||
- No long-press or context menu — keep interaction model simple and consistent
|
||||
|
||||
### History view format
|
||||
- Bottom sheet (showModalBottomSheet) — consistent with existing template_picker_sheet and icon_picker_sheet patterns
|
||||
- Each entry shows: date formatted as "dd.MM.yyyy" and time as "HH:mm" — German locale
|
||||
- Entries listed reverse-chronological (newest first)
|
||||
- No grouping or pagination — household tasks won't have thousands of completions; simple ListView is sufficient
|
||||
|
||||
### Empty state
|
||||
- When task has never been completed: centered icon (e.g., Icons.history) + "Noch nie erledigt" message — meaningful, not just blank
|
||||
- No special state for many completions — just scroll
|
||||
|
||||
### Claude's Discretion
|
||||
- Exact bottom sheet height and styling
|
||||
- Whether to show a completion count summary at the top of the sheet
|
||||
- Animation and transition details
|
||||
- DAO query structure (single method returning List<TaskCompletion>)
|
||||
- Whether CalendarTaskRow onTap goes to edit form or directly to history
|
||||
|
||||
</decisions>
|
||||
|
||||
<specifics>
|
||||
## Specific Ideas
|
||||
|
||||
No specific requirements — user chose "You decide." Open to standard approaches that match existing app patterns.
|
||||
|
||||
</specifics>
|
||||
|
||||
<code_context>
|
||||
## Existing Code Insights
|
||||
|
||||
### Reusable Assets
|
||||
- `TaskCompletions` table: Already exists in database.dart (id, taskId, completedAt) — no schema change needed
|
||||
- `TasksDao.completeTask()`: Already inserts into taskCompletions on every completion — HIST-01 data recording is done
|
||||
- `showModalBottomSheet`: Used by template_picker_sheet.dart and icon_picker_sheet.dart — established pattern for overlays
|
||||
- `AppLocalizations` + `.arb` files: German-only localization pipeline in place
|
||||
|
||||
### Established Patterns
|
||||
- DAOs extend `DatabaseAccessor<AppDatabase>` with `@DriftAccessor` annotation
|
||||
- Riverpod `StreamProvider.autoDispose` or `FutureProvider` for reactive data
|
||||
- Feature folder structure: `features/tasks/data/`, `domain/`, `presentation/`
|
||||
- Bottom sheets use `showModalBottomSheet` with `DraggableScrollableSheet` or simple `Column`
|
||||
|
||||
### Integration Points
|
||||
- `TaskFormScreen` (edit mode): Entry point for history — add a row/button when `isEditing`
|
||||
- `TasksDao`: Add `watchCompletionsForTask(int taskId)` or `getCompletionsForTask(int taskId)` method
|
||||
- `CalendarTaskRow`: Currently no onTap — needs navigation to task edit form for history access
|
||||
- `router.dart`: Route `/rooms/:roomId/tasks/:taskId` already exists for TaskFormScreen — no new route needed if using bottom sheet
|
||||
- `app_de.arb`: Add localization strings for history UI labels
|
||||
|
||||
</code_context>
|
||||
|
||||
<deferred>
|
||||
## Deferred Ideas
|
||||
|
||||
None — discussion stayed within phase scope
|
||||
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 06-task-history*
|
||||
*Context gathered: 2026-03-16*
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
phase: 06-task-history
|
||||
verified: 2026-03-16T22:15:00Z
|
||||
status: passed
|
||||
score: 3/3 must-haves verified
|
||||
re_verification: false
|
||||
---
|
||||
|
||||
# Phase 6: Task History Verification Report
|
||||
|
||||
**Phase Goal:** Users can see exactly when each task was completed in the past, building trust that the scheduling loop is working correctly
|
||||
**Verified:** 2026-03-16T22:15:00Z
|
||||
**Status:** PASSED
|
||||
**Re-verification:** No — initial verification
|
||||
|
||||
---
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Observable Truths
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|---|-------|--------|----------|
|
||||
| 1 | Every task completion is recorded with a timestamp and persists across app restarts | VERIFIED | `watchCompletionsForTask` reads from `TaskCompletions` table (persistent SQLite); `completeTask` already wrote timestamps; 5 DAO tests confirm stream returns correct data including stream reactivity test |
|
||||
| 2 | User can open a history view from the task edit form showing all past completion dates in reverse-chronological order | VERIFIED | `task_form_screen.dart` lines 192-204: `if (widget.isEditing)` guard shows `ListTile` with `onTap: () => showTaskHistorySheet(...)`. Sheet uses `StreamBuilder` on `watchCompletionsForTask` with `..orderBy([(c) => OrderingTerm.desc(c.completedAt)])`, renders dates as `dd.MM.yyyy` + `HH:mm` via intl |
|
||||
| 3 | History view shows a meaningful empty state if the task has never been completed | VERIFIED | `task_history_sheet.dart` lines 70-87: `if (completions.isEmpty)` branch renders `Icon(Icons.history, size: 48)` + `Text(l10n.taskHistoryEmpty)` ("Noch nie erledigt") |
|
||||
|
||||
**Score:** 3/3 truths verified
|
||||
|
||||
---
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Provides | Status | Details |
|
||||
|----------|---------|--------|---------|
|
||||
| `lib/features/tasks/data/tasks_dao.dart` | `watchCompletionsForTask(int taskId)` stream method | VERIFIED | Method exists at line 85, returns `Stream<List<TaskCompletion>>`, ordered by `completedAt DESC`, 110 lines total |
|
||||
| `lib/features/tasks/presentation/task_history_sheet.dart` | Bottom sheet displaying task completion history | VERIFIED | 137 lines, exports top-level `showTaskHistorySheet()`, `_TaskHistorySheet` is a `ConsumerWidget` with full StreamBuilder, empty state, date list |
|
||||
| `lib/features/tasks/presentation/task_form_screen.dart` | Verlauf button in edit mode opening history sheet | VERIFIED | Imports `task_history_sheet.dart` (line 13), `showTaskHistorySheet` called at line 199, guarded by `if (widget.isEditing)` |
|
||||
| `lib/features/home/presentation/calendar_task_row.dart` | onTap navigation to task edit form | VERIFIED | `ListTile.onTap` at line 39 calls `context.go('/rooms/${taskWithRoom.roomId}/tasks/${taskWithRoom.task.id}')` |
|
||||
| `test/features/tasks/data/task_history_dao_test.dart` | Tests for completion history DAO query | VERIFIED | 158 lines, 5 tests: empty state, single completion, multiple reverse-chronological, task isolation, stream reactivity — all pass |
|
||||
| `lib/features/tasks/data/tasks_dao.g.dart` | Drift-generated mixin (build_runner output) | VERIFIED | Exists, 25 lines, regenerated with `taskCompletions` table accessor present |
|
||||
|
||||
---
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| From | To | Via | Status | Details |
|
||||
|------|----|-----|--------|---------|
|
||||
| `task_form_screen.dart` | `task_history_sheet.dart` | `showTaskHistorySheet` call in Verlauf `onTap` | WIRED | Import at line 13; called at line 199 inside `if (widget.isEditing)` block |
|
||||
| `task_history_sheet.dart` | `tasks_dao.dart` | `watchCompletionsForTask` stream consumption | WIRED | `ref.read(appDatabaseProvider).tasksDao.watchCompletionsForTask(taskId)` at lines 59-62; stream result consumed by `StreamBuilder` builder |
|
||||
| `calendar_task_row.dart` | `TaskFormScreen` | GoRouter navigation on row tap | WIRED | `context.go('/rooms/${taskWithRoom.roomId}/tasks/${taskWithRoom.task.id}')` at line 39-41; route `/rooms/:roomId/tasks/:taskId` resolves to `TaskFormScreen` per router.dart |
|
||||
|
||||
---
|
||||
|
||||
### Requirements Coverage
|
||||
|
||||
| Requirement | Source Plan | Description | Status | Evidence |
|
||||
|-------------|------------|-------------|--------|----------|
|
||||
| HIST-01 | 06-01-PLAN.md | Each task completion is recorded with a timestamp | SATISFIED | `TasksDao.completeTask()` inserts into `TaskCompletions` (pre-existing); `watchCompletionsForTask` surfaces data; 5 DAO tests confirm timestamps are stored and retrieved correctly |
|
||||
| HIST-02 | 06-01-PLAN.md | User can view past completion dates for any individual task | SATISFIED | Full UI chain: `CalendarTaskRow.onTap` -> `TaskFormScreen` (edit mode) -> "Verlauf" `ListTile` -> `showTaskHistorySheet` -> `_TaskHistorySheet` StreamBuilder showing reverse-chronological German-formatted dates |
|
||||
|
||||
No orphaned requirements — REQUIREMENTS.md Traceability table shows only HIST-01 and HIST-02 mapped to Phase 6, both accounted for and marked Complete.
|
||||
|
||||
---
|
||||
|
||||
### Anti-Patterns Found
|
||||
|
||||
None. No TODOs, FIXMEs, placeholder returns, empty handlers, or stub implementations found in any of the 5 modified source files.
|
||||
|
||||
---
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
#### 1. Tap-to-edit navigation in running app
|
||||
|
||||
**Test:** Launch app, ensure at least one task exists on the calendar, tap the task row (not the checkbox).
|
||||
**Expected:** App navigates to `TaskFormScreen` in edit mode showing the task's fields and a "Verlauf" row at the bottom.
|
||||
**Why human:** GoRouter navigation with `context.go` cannot be verified by static analysis; requires runtime rendering.
|
||||
|
||||
#### 2. History sheet opens with correct content
|
||||
|
||||
**Test:** In `TaskFormScreen` edit mode, tap the "Verlauf" ListTile.
|
||||
**Expected:** Bottom sheet slides up showing either: (a) the empty state with a history icon and "Noch nie erledigt", or (b) a list of past completions with `dd.MM.yyyy` dates as titles and `HH:mm` times as subtitles, newest first.
|
||||
**Why human:** `showModalBottomSheet` rendering and visual layout cannot be verified by static analysis.
|
||||
|
||||
#### 3. Live update after completing a task
|
||||
|
||||
**Test:** Complete a task via checkbox in the calendar, then navigate to that task's edit form and tap "Verlauf".
|
||||
**Expected:** The newly recorded completion appears at the top of the history sheet with today's date and approximate current time.
|
||||
**Why human:** Real-time stream reactivity through the full UI stack (checkbox -> DAO write -> stream emit -> sheet UI update) requires runtime observation.
|
||||
|
||||
---
|
||||
|
||||
### Verification Summary
|
||||
|
||||
All automated checks passed with no gaps found.
|
||||
|
||||
**Test suite:** 106/106 tests pass (101 pre-existing + 5 new DAO tests covering all specified behaviors).
|
||||
**Static analysis:** `flutter analyze --no-fatal-infos` — zero issues.
|
||||
**Commits verified:** All three phase commits exist (`2687f5e`, `ceae7d7`, `9f902ff`) with expected file changes.
|
||||
|
||||
The full feature chain is intact:
|
||||
- `TaskCompletions` table stores timestamps (HIST-01, pre-existing from data layer)
|
||||
- `watchCompletionsForTask` surfaces completions as a live Drift stream
|
||||
- `task_history_sheet.dart` renders them in German locale with reverse-chronological ordering and a meaningful empty state
|
||||
- `TaskFormScreen` (edit mode only) provides the "Verlauf" entry point
|
||||
- `CalendarTaskRow` onTap makes history reachable from the home calendar in two taps
|
||||
|
||||
Three human-only items remain for final sign-off: tap navigation, sheet rendering, and live update after completion.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-03-16T22:15:00Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
Reference in New Issue
Block a user