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:
2026-03-16 23:32:04 +01:00
parent 0ea79e0853
commit edce11dd78
24 changed files with 319 additions and 123 deletions

View 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&lt;void&gt; 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>

View File

@@ -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*

View File

@@ -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*

View File

@@ -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)_