diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 56b3f01..6362e52 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -43,6 +43,10 @@ See `milestones/v1.1-ROADMAP.md` for full phase details.
**Goal**: Users can remove tasks they no longer need, with smart preservation of completion history for future statistics
**Depends on**: Phase 7 (v1.1 shipped — calendar, history, and sorting all in place)
**Requirements**: DEL-01, DEL-02, DEL-03, DEL-04
+**Plans:** 2 plans
+Plans:
+- [ ] 08-01-PLAN.md — Data layer: isActive column, schema migration, DAO filters and methods
+- [ ] 08-02-PLAN.md — UI layer: delete button, confirmation dialog, smart delete provider
**Success Criteria** (what must be TRUE):
1. The task edit form has a clearly visible delete action (button or icon)
2. Deleting a task with zero completions removes it from the database entirely
@@ -82,6 +86,6 @@ See `milestones/v1.1-ROADMAP.md` for full phase details.
| 5. Calendar Strip | v1.1 | 2/2 | Complete | 2026-03-16 |
| 6. Task History | v1.1 | 1/1 | Complete | 2026-03-16 |
| 7. Task Sorting | v1.1 | 2/2 | Complete | 2026-03-16 |
-| 8. Task Delete | v1.2 | - | Planned | - |
+| 8. Task Delete | v1.2 | 0/2 | In Progress | - |
| 9. Task Creation UX | v1.2 | - | Planned | - |
| 10. Dead Code Cleanup | v1.2 | - | Planned | - |
diff --git a/.planning/phases/08-task-delete/08-01-PLAN.md b/.planning/phases/08-task-delete/08-01-PLAN.md
new file mode 100644
index 0000000..6a107e9
--- /dev/null
+++ b/.planning/phases/08-task-delete/08-01-PLAN.md
@@ -0,0 +1,310 @@
+---
+phase: 08-task-delete
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - lib/core/database/database.dart
+ - lib/features/tasks/data/tasks_dao.dart
+ - lib/features/home/data/calendar_dao.dart
+ - lib/features/home/data/daily_plan_dao.dart
+ - lib/features/rooms/data/rooms_dao.dart
+ - test/features/tasks/data/tasks_dao_test.dart
+autonomous: true
+requirements: [DEL-02, DEL-03]
+
+must_haves:
+ truths:
+ - "Active tasks appear in all views (calendar, room task lists, daily plan)"
+ - "Deactivated tasks are hidden from all views"
+ - "Hard delete removes task and completions from DB entirely"
+ - "Soft delete sets isActive to false without removing data"
+ - "Existing tasks default to active after migration"
+ artifacts:
+ - path: "lib/core/database/database.dart"
+ provides: "isActive BoolColumn on Tasks table, schema v3, migration"
+ contains: "isActive"
+ - path: "lib/features/tasks/data/tasks_dao.dart"
+ provides: "softDeleteTask, getCompletionCount, isActive filter on watchTasksInRoom"
+ exports: ["softDeleteTask", "getCompletionCount"]
+ - path: "lib/features/home/data/calendar_dao.dart"
+ provides: "isActive=true filter on all 6 task queries + getTaskCount"
+ contains: "isActive"
+ - path: "lib/features/home/data/daily_plan_dao.dart"
+ provides: "isActive=true filter on watchAllTasksWithRoomName and count queries"
+ contains: "isActive"
+ - path: "lib/features/rooms/data/rooms_dao.dart"
+ provides: "isActive=true filter on task queries in watchRoomWithStats"
+ contains: "isActive"
+ - path: "test/features/tasks/data/tasks_dao_test.dart"
+ provides: "Tests for softDeleteTask, getCompletionCount, isActive filtering"
+ key_links:
+ - from: "lib/core/database/database.dart"
+ to: "All DAOs"
+ via: "Tasks table schema with isActive column"
+ pattern: "BoolColumn.*isActive.*withDefault.*true"
+ - from: "lib/features/tasks/data/tasks_dao.dart"
+ to: "lib/features/tasks/presentation/task_providers.dart"
+ via: "softDeleteTask and getCompletionCount methods"
+ pattern: "softDeleteTask|getCompletionCount"
+---
+
+
+Add isActive column to the Tasks table and filter all DAO queries to exclude deactivated tasks.
+
+Purpose: Foundation for smart task deletion — the isActive column enables soft-delete behavior where completed tasks are hidden but preserved for statistics, while hard-delete removes tasks with no history entirely.
+
+Output: Schema v3 with isActive column, all DAO queries filtering active-only, softDeleteTask and getCompletionCount DAO methods, passing tests.
+
+
+
+@/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/08-task-delete/08-CONTEXT.md
+
+
+
+
+From lib/core/database/database.dart (current schema):
+```dart
+class Tasks extends Table {
+ IntColumn get id => integer().autoIncrement()();
+ IntColumn get roomId => integer().references(Rooms, #id)();
+ TextColumn get name => text().withLength(min: 1, max: 200)();
+ TextColumn get description => text().nullable()();
+ IntColumn get intervalType => intEnum()();
+ IntColumn get intervalDays => integer().withDefault(const Constant(1))();
+ IntColumn get anchorDay => integer().nullable()();
+ IntColumn get effortLevel => intEnum()();
+ DateTimeColumn get nextDueDate => dateTime()();
+ DateTimeColumn get createdAt => dateTime().clientDefault(() => DateTime.now())();
+}
+
+// Current schema version
+int get schemaVersion => 2;
+
+// Current migration strategy
+MigrationStrategy get migration {
+ return MigrationStrategy(
+ onCreate: (Migrator m) async { await m.createAll(); },
+ onUpgrade: (Migrator m, int from, int to) async {
+ if (from < 2) {
+ await m.createTable(rooms);
+ await m.createTable(tasks);
+ await m.createTable(taskCompletions);
+ }
+ },
+ beforeOpen: (details) async {
+ await customStatement('PRAGMA foreign_keys = ON');
+ },
+ );
+}
+```
+
+From lib/features/tasks/data/tasks_dao.dart (existing methods):
+```dart
+class TasksDao extends DatabaseAccessor with _$TasksDaoMixin {
+ Stream> watchTasksInRoom(int roomId);
+ Future insertTask(TasksCompanion task);
+ Future updateTask(Task task);
+ Future deleteTask(int taskId); // hard delete with cascade
+ Future completeTask(int taskId, {DateTime? now});
+ Stream> watchCompletionsForTask(int taskId);
+ Future getOverdueTaskCount(int roomId, {DateTime? today});
+}
+```
+
+From lib/features/home/data/calendar_dao.dart (6 queries needing filter):
+```dart
+class CalendarDao extends DatabaseAccessor with _$CalendarDaoMixin {
+ Stream> watchTasksForDate(DateTime date);
+ Stream> watchTasksForDateInRoom(DateTime date, int roomId);
+ Stream> watchOverdueTasks(DateTime referenceDate);
+ Stream> watchOverdueTasksInRoom(DateTime referenceDate, int roomId);
+ Future getTaskCount();
+ Future getTaskCountInRoom(int roomId);
+}
+```
+
+From lib/features/home/data/daily_plan_dao.dart (3 queries needing filter):
+```dart
+class DailyPlanDao extends DatabaseAccessor with _$DailyPlanDaoMixin {
+ Stream> watchAllTasksWithRoomName();
+ Future getOverdueAndTodayTaskCount({DateTime? today});
+ Future getOverdueTaskCount({DateTime? today});
+}
+```
+
+From lib/features/rooms/data/rooms_dao.dart (task query in watchRoomWithStats):
+```dart
+// Inside watchRoomWithStats:
+final taskList = await (select(tasks)
+ ..where((t) => t.roomId.equals(room.id)))
+ .get();
+```
+
+Test pattern from test/features/tasks/data/tasks_dao_test.dart:
+```dart
+late AppDatabase db;
+late int roomId;
+setUp(() async {
+ db = AppDatabase(NativeDatabase.memory());
+ roomId = await db.roomsDao.insertRoom(
+ RoomsCompanion.insert(name: 'Kueche', iconName: 'kitchen'),
+ );
+});
+tearDown(() async { await db.close(); });
+```
+
+
+
+
+
+
+ Task 1: Add isActive column, migration, and new DAO methods
+
+ lib/core/database/database.dart,
+ lib/features/tasks/data/tasks_dao.dart,
+ test/features/tasks/data/tasks_dao_test.dart
+
+
+ - Test: softDeleteTask sets isActive to false (task remains in DB but isActive == false)
+ - Test: getCompletionCount returns 0 for task with no completions
+ - Test: getCompletionCount returns correct count for task with completions
+ - Test: watchTasksInRoom excludes tasks where isActive is false
+ - Test: getOverdueTaskCount excludes tasks where isActive is false
+ - Test: existing hard deleteTask still works (removes task and completions)
+
+
+1. In database.dart Tasks table, add: `BoolColumn get isActive => boolean().withDefault(const Constant(true))();`
+
+2. Bump schemaVersion to 3.
+
+3. Update migration onUpgrade — add `from < 3` block:
+ ```dart
+ if (from < 3) {
+ await m.addColumn(tasks, tasks.isActive);
+ }
+ ```
+ This uses Drift's addColumn which handles the ALTER TABLE and the default value for existing rows.
+
+4. In tasks_dao.dart, add isActive filter to watchTasksInRoom:
+ ```dart
+ ..where((t) => t.roomId.equals(roomId) & t.isActive.equals(true))
+ ```
+
+5. In tasks_dao.dart, add isActive filter to getOverdueTaskCount task query:
+ ```dart
+ ..where((t) => t.roomId.equals(roomId) & t.isActive.equals(true))
+ ```
+
+6. Add softDeleteTask method to TasksDao:
+ ```dart
+ Future softDeleteTask(int taskId) {
+ return (update(tasks)..where((t) => t.id.equals(taskId)))
+ .write(const TasksCompanion(isActive: Value(false)));
+ }
+ ```
+
+7. Add getCompletionCount method to TasksDao:
+ ```dart
+ Future getCompletionCount(int taskId) async {
+ final count = taskCompletions.id.count();
+ final query = selectOnly(taskCompletions)
+ ..addColumns([count])
+ ..where(taskCompletions.taskId.equals(taskId));
+ final result = await query.getSingle();
+ return result.read(count) ?? 0;
+ }
+ ```
+
+8. Run `dart run build_runner build --delete-conflicting-outputs` to regenerate Drift code.
+
+9. Write tests in tasks_dao_test.dart following existing test patterns (NativeDatabase.memory, setUp/tearDown).
+
+
+ cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/data/tasks_dao_test.dart --reporter compact
+
+
+ - Tasks table has isActive BoolColumn with default true
+ - Schema version is 3 with working migration
+ - softDeleteTask sets isActive=false without removing data
+ - getCompletionCount returns accurate count
+ - watchTasksInRoom only returns active tasks
+ - getOverdueTaskCount only counts active tasks
+ - All new tests pass, all existing tests pass
+
+
+
+
+ Task 2: Add isActive filters to CalendarDao, DailyPlanDao, and RoomsDao
+
+ lib/features/home/data/calendar_dao.dart,
+ lib/features/home/data/daily_plan_dao.dart,
+ lib/features/rooms/data/rooms_dao.dart
+
+
+1. In calendar_dao.dart, add `& tasks.isActive.equals(true)` to the WHERE clause of ALL 6 query methods:
+ - watchTasksForDate: add to existing `query.where(...)` expression
+ - watchTasksForDateInRoom: add to existing `query.where(...)` expression
+ - watchOverdueTasks: add to existing `query.where(...)` expression
+ - watchOverdueTasksInRoom: add to existing `query.where(...)` expression
+ - getTaskCount: add `..where(tasks.isActive.equals(true))` to selectOnly
+ - getTaskCountInRoom: add `& tasks.isActive.equals(true)` to existing where
+
+2. In daily_plan_dao.dart, add isActive filter to all 3 query methods:
+ - watchAllTasksWithRoomName: add `query.where(tasks.isActive.equals(true));` after the join
+ - getOverdueAndTodayTaskCount: add `& tasks.isActive.equals(true)` to existing where
+ - getOverdueTaskCount: add `& tasks.isActive.equals(true)` to existing where
+
+3. In rooms_dao.dart watchRoomWithStats method, filter the task query to active-only:
+ ```dart
+ final taskList = await (select(tasks)
+ ..where((t) => t.roomId.equals(room.id) & t.isActive.equals(true)))
+ .get();
+ ```
+
+4. Run `dart run build_runner build --delete-conflicting-outputs` to regenerate if needed.
+
+5. Run `dart analyze` to confirm no issues.
+
+
+ cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test --reporter compact && dart analyze --fatal-infos
+
+
+ - All 6 CalendarDao queries filter by isActive=true
+ - All 3 DailyPlanDao queries filter by isActive=true
+ - RoomsDao watchRoomWithStats only counts active tasks
+ - All 137+ existing tests still pass
+ - dart analyze reports zero issues
+
+
+
+
+
+
+- Schema version is 3, migration adds isActive column with default true
+- softDeleteTask and getCompletionCount methods exist on TasksDao
+- Every query across TasksDao, CalendarDao, DailyPlanDao, and RoomsDao that returns tasks filters by isActive=true
+- Hard deleteTask (cascade) still works unchanged
+- All tests pass: `flutter test --reporter compact`
+- Code quality: `dart analyze --fatal-infos` reports zero issues
+
+
+
+- Deactivated tasks (isActive=false) are excluded from ALL active views: calendar day tasks, overdue tasks, room task lists, daily plan, room stats
+- Existing tasks default to active after schema migration
+- New DAO methods (softDeleteTask, getCompletionCount) are available for the UI layer
+- All 137+ tests pass, new DAO tests pass
+
+
+
diff --git a/.planning/phases/08-task-delete/08-02-PLAN.md b/.planning/phases/08-task-delete/08-02-PLAN.md
new file mode 100644
index 0000000..3c13b14
--- /dev/null
+++ b/.planning/phases/08-task-delete/08-02-PLAN.md
@@ -0,0 +1,290 @@
+---
+phase: 08-task-delete
+plan: 02
+type: execute
+wave: 2
+depends_on: ["08-01"]
+files_modified:
+ - lib/features/tasks/presentation/task_providers.dart
+ - lib/features/tasks/presentation/task_form_screen.dart
+autonomous: true
+requirements: [DEL-01, DEL-04]
+
+must_haves:
+ truths:
+ - "User sees a red delete button at the bottom of the task edit form"
+ - "Tapping delete shows a confirmation dialog before any action"
+ - "Confirming delete on a task with no completions removes it from the database"
+ - "Confirming delete on a task with completions deactivates it (hidden from views)"
+ - "After deletion the user is navigated back to the room task list"
+ artifacts:
+ - path: "lib/features/tasks/presentation/task_form_screen.dart"
+ provides: "Delete button and confirmation dialog in edit mode"
+ contains: "taskDeleteConfirmTitle"
+ - path: "lib/features/tasks/presentation/task_providers.dart"
+ provides: "Smart delete method using getCompletionCount"
+ contains: "softDeleteTask"
+ key_links:
+ - from: "lib/features/tasks/presentation/task_form_screen.dart"
+ to: "lib/features/tasks/presentation/task_providers.dart"
+ via: "TaskActions.smartDeleteTask call from delete button callback"
+ pattern: "smartDeleteTask"
+ - from: "lib/features/tasks/presentation/task_providers.dart"
+ to: "lib/features/tasks/data/tasks_dao.dart"
+ via: "getCompletionCount + conditional deleteTask or softDeleteTask"
+ pattern: "getCompletionCount.*softDeleteTask|deleteTask"
+---
+
+
+Add the delete button and confirmation dialog to the task edit form, with smart delete logic in the provider layer.
+
+Purpose: Users can remove tasks they no longer need. The smart behavior (hard vs soft delete) is invisible to the user -- they just see "delete" with a confirmation.
+
+Output: Working delete flow on the task edit form: red button -> confirmation dialog -> smart delete -> navigate back.
+
+
+
+@/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/08-task-delete/08-CONTEXT.md
+@.planning/phases/08-task-delete/08-01-SUMMARY.md
+
+
+
+
+From lib/features/tasks/presentation/task_providers.dart (existing TaskActions):
+```dart
+@riverpod
+class TaskActions extends _$TaskActions {
+ @override
+ FutureOr build() {}
+
+ Future createTask({...}) async { ... }
+ Future updateTask(Task task) async { ... }
+ Future deleteTask(int taskId) async { ... } // calls DAO hard delete
+ Future completeTask(int taskId) async { ... }
+}
+```
+
+From lib/features/tasks/data/tasks_dao.dart (after Plan 01):
+```dart
+class TasksDao {
+ Future deleteTask(int taskId); // hard delete (cascade)
+ Future softDeleteTask(int taskId); // sets isActive = false
+ Future getCompletionCount(int taskId); // count completions
+}
+```
+
+From lib/features/tasks/presentation/task_form_screen.dart (edit mode section):
+```dart
+// History section (edit mode only) — delete button goes AFTER this
+if (widget.isEditing) ...[
+ 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!,
+ ),
+ ),
+],
+```
+
+From lib/l10n/app_de.arb (existing delete l10n strings):
+```json
+"taskDeleteConfirmTitle": "Aufgabe l\u00f6schen?",
+"taskDeleteConfirmMessage": "Die Aufgabe wird unwiderruflich gel\u00f6scht.",
+"taskDeleteConfirmAction": "L\u00f6schen"
+```
+
+Room delete dialog pattern (from lib/features/rooms/presentation/rooms_screen.dart:165-189):
+```dart
+showDialog(
+ context: context,
+ builder: (ctx) => AlertDialog(
+ title: Text(l10n.roomDeleteConfirmTitle),
+ content: Text(l10n.roomDeleteConfirmMessage),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(ctx),
+ child: Text(l10n.cancel),
+ ),
+ FilledButton(
+ style: FilledButton.styleFrom(
+ backgroundColor: Theme.of(ctx).colorScheme.error,
+ ),
+ onPressed: () { ... },
+ child: Text(l10n.roomDeleteConfirmAction),
+ ),
+ ],
+ ),
+);
+```
+
+
+
+
+
+
+ Task 1: Add smartDeleteTask to TaskActions provider
+ lib/features/tasks/presentation/task_providers.dart
+
+Add a `smartDeleteTask` method to the `TaskActions` class in task_providers.dart. This method checks the completion count and routes to hard delete or soft delete accordingly:
+
+```dart
+/// Smart delete: hard-deletes tasks with no completions, soft-deletes tasks with completions.
+Future smartDeleteTask(int taskId) async {
+ final db = ref.read(appDatabaseProvider);
+ final completionCount = await db.tasksDao.getCompletionCount(taskId);
+ if (completionCount == 0) {
+ await db.tasksDao.deleteTask(taskId);
+ } else {
+ await db.tasksDao.softDeleteTask(taskId);
+ }
+}
+```
+
+Keep the existing `deleteTask` method unchanged (it is still a valid hard delete for other uses like room cascade delete).
+
+Run `dart run build_runner build --delete-conflicting-outputs` to regenerate the provider code.
+
+
+ cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze --fatal-infos lib/features/tasks/presentation/task_providers.dart
+
+
+ - smartDeleteTask method exists on TaskActions
+ - Method checks completion count and routes to hard or soft delete
+ - dart analyze passes with zero issues
+
+
+
+
+ Task 2: Add delete button and confirmation dialog to TaskFormScreen
+ lib/features/tasks/presentation/task_form_screen.dart
+
+1. In the TaskFormScreen build method's ListView children, AFTER the history section (the existing `if (widget.isEditing) ...` block ending at line ~204), add the delete button section inside the same `if (widget.isEditing)` block:
+
+```dart
+if (widget.isEditing) ...[
+ const SizedBox(height: 24),
+ const Divider(),
+ // History ListTile (existing)
+ ListTile(
+ leading: const Icon(Icons.history),
+ title: Text(l10n.taskHistoryTitle),
+ trailing: const Icon(Icons.chevron_right),
+ onTap: () => showTaskHistorySheet(
+ context: context,
+ taskId: widget.taskId!,
+ ),
+ ),
+ // DELETE BUTTON — new
+ const Divider(),
+ const SizedBox(height: 8),
+ SizedBox(
+ width: double.infinity,
+ child: FilledButton.icon(
+ style: FilledButton.styleFrom(
+ backgroundColor: theme.colorScheme.error,
+ foregroundColor: theme.colorScheme.onError,
+ ),
+ onPressed: _isLoading ? null : _onDelete,
+ icon: const Icon(Icons.delete_outline),
+ label: Text(l10n.taskDeleteConfirmAction),
+ ),
+ ),
+],
+```
+
+2. Add a `_onDelete` method to _TaskFormScreenState:
+
+```dart
+Future _onDelete() async {
+ final l10n = AppLocalizations.of(context);
+ final confirmed = await showDialog(
+ context: context,
+ builder: (ctx) => AlertDialog(
+ title: Text(l10n.taskDeleteConfirmTitle),
+ content: Text(l10n.taskDeleteConfirmMessage),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(ctx, false),
+ child: Text(l10n.cancel),
+ ),
+ FilledButton(
+ style: FilledButton.styleFrom(
+ backgroundColor: Theme.of(ctx).colorScheme.error,
+ ),
+ onPressed: () => Navigator.pop(ctx, true),
+ child: Text(l10n.taskDeleteConfirmAction),
+ ),
+ ],
+ ),
+ );
+
+ if (confirmed != true || !mounted) return;
+
+ setState(() => _isLoading = true);
+ try {
+ await ref.read(taskActionsProvider.notifier).smartDeleteTask(widget.taskId!);
+ if (mounted) {
+ context.pop();
+ }
+ } finally {
+ if (mounted) {
+ setState(() => _isLoading = false);
+ }
+ }
+}
+```
+
+Note: The `l10n.cancel` string should already exist from the room delete dialog. If not, use `MaterialLocalizations.of(context).cancelButtonLabel`.
+
+3. Verify `cancel` l10n key exists. If it does not exist in app_de.arb, check for the existing cancel button pattern in rooms_screen.dart and use the same approach.
+
+
+ cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test --reporter compact && dart analyze --fatal-infos
+
+
+ - Red delete button visible at bottom of task edit form (below history, separated by divider)
+ - Delete button only shows in edit mode (not create mode)
+ - Tapping delete shows AlertDialog with title "Aufgabe loschen?" and error-colored confirm button
+ - Canceling dialog does nothing
+ - Confirming dialog calls smartDeleteTask and pops back to room task list
+ - Button is disabled while loading (_isLoading)
+ - All existing tests pass, dart analyze clean
+
+
+
+
+
+
+- Task edit form shows red delete button below history section with divider separator
+- Delete button is NOT shown in create mode
+- Tapping delete shows confirmation dialog matching room delete dialog pattern
+- Confirming deletes/deactivates the task and navigates back
+- Canceling returns to the form without changes
+- All tests pass: `flutter test --reporter compact`
+- Code quality: `dart analyze --fatal-infos` reports zero issues
+
+
+
+- Complete delete flow works: open task -> scroll to bottom -> tap delete -> confirm -> back to room task list
+- Smart delete is invisible to user: tasks with completions are deactivated, tasks without are removed
+- Delete button follows Material 3 error color pattern
+- Confirmation dialog uses existing German l10n strings
+- All 137+ tests pass
+
+
+