---
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"
---

<objective>
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.
</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/08-task-delete/08-CONTEXT.md

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->

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<IntervalType>()();
  IntColumn get intervalDays => integer().withDefault(const Constant(1))();
  IntColumn get anchorDay => integer().nullable()();
  IntColumn get effortLevel => intEnum<EffortLevel>()();
  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<AppDatabase> with _$TasksDaoMixin {
  Stream<List<Task>> watchTasksInRoom(int roomId);
  Future<int> insertTask(TasksCompanion task);
  Future<bool> updateTask(Task task);
  Future<void> deleteTask(int taskId);  // hard delete with cascade
  Future<void> completeTask(int taskId, {DateTime? now});
  Stream<List<TaskCompletion>> watchCompletionsForTask(int taskId);
  Future<int> getOverdueTaskCount(int roomId, {DateTime? today});
}
```

From lib/features/home/data/calendar_dao.dart (6 queries needing filter):
```dart
class CalendarDao extends DatabaseAccessor<AppDatabase> with _$CalendarDaoMixin {
  Stream<List<TaskWithRoom>> watchTasksForDate(DateTime date);
  Stream<List<TaskWithRoom>> watchTasksForDateInRoom(DateTime date, int roomId);
  Stream<List<TaskWithRoom>> watchOverdueTasks(DateTime referenceDate);
  Stream<List<TaskWithRoom>> watchOverdueTasksInRoom(DateTime referenceDate, int roomId);
  Future<int> getTaskCount();
  Future<int> getTaskCountInRoom(int roomId);
}
```

From lib/features/home/data/daily_plan_dao.dart (3 queries needing filter):
```dart
class DailyPlanDao extends DatabaseAccessor<AppDatabase> with _$DailyPlanDaoMixin {
  Stream<List<TaskWithRoom>> watchAllTasksWithRoomName();
  Future<int> getOverdueAndTodayTaskCount({DateTime? today});
  Future<int> 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(); });
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Add isActive column, migration, and new DAO methods</name>
  <files>
    lib/core/database/database.dart,
    lib/features/tasks/data/tasks_dao.dart,
    test/features/tasks/data/tasks_dao_test.dart
  </files>
  <behavior>
    - 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)
  </behavior>
  <action>
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<void> 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<int> 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).
  </action>
  <verify>
    <automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/data/tasks_dao_test.dart --reporter compact</automated>
  </verify>
  <done>
    - 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
  </done>
</task>

<task type="auto">
  <name>Task 2: Add isActive filters to CalendarDao, DailyPlanDao, and RoomsDao</name>
  <files>
    lib/features/home/data/calendar_dao.dart,
    lib/features/home/data/daily_plan_dao.dart,
    lib/features/rooms/data/rooms_dao.dart
  </files>
  <action>
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.
  </action>
  <verify>
    <automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test --reporter compact && dart analyze --fatal-infos</automated>
  </verify>
  <done>
    - 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
  </done>
</task>

</tasks>

<verification>
- 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
</verification>

<success_criteria>
- 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
</success_criteria>

<output>
After completion, create `.planning/phases/08-task-delete/08-01-SUMMARY.md`
</output>
