Files

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
08-task-delete 01 execute 1
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
true
DEL-02
DEL-03
truths artifacts key_links
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
path provides contains
lib/core/database/database.dart isActive BoolColumn on Tasks table, schema v3, migration isActive
path provides exports
lib/features/tasks/data/tasks_dao.dart softDeleteTask, getCompletionCount, isActive filter on watchTasksInRoom
softDeleteTask
getCompletionCount
path provides contains
lib/features/home/data/calendar_dao.dart isActive=true filter on all 6 task queries + getTaskCount isActive
path provides contains
lib/features/home/data/daily_plan_dao.dart isActive=true filter on watchAllTasksWithRoomName and count queries isActive
path provides contains
lib/features/rooms/data/rooms_dao.dart isActive=true filter on task queries in watchRoomWithStats isActive
path provides
test/features/tasks/data/tasks_dao_test.dart Tests for softDeleteTask, getCompletionCount, isActive filtering
from to via pattern
lib/core/database/database.dart All DAOs Tasks table schema with isActive column BoolColumn.*isActive.*withDefault.*true
from to via pattern
lib/features/tasks/data/tasks_dao.dart lib/features/tasks/presentation/task_providers.dart softDeleteTask and getCompletionCount methods 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.

<execution_context> @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md </execution_context>

@.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):

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):

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):

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):

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):

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

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))();`
  1. Bump schemaVersion to 3.

  2. Update migration onUpgrade — add from < 3 block:

    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.

  3. In tasks_dao.dart, add isActive filter to watchTasksInRoom:

    ..where((t) => t.roomId.equals(roomId) & t.isActive.equals(true))
    
  4. In tasks_dao.dart, add isActive filter to getOverdueTaskCount task query:

    ..where((t) => t.roomId.equals(roomId) & t.isActive.equals(true))
    
  5. Add softDeleteTask method to TasksDao:

    Future<void> softDeleteTask(int taskId) {
      return (update(tasks)..where((t) => t.id.equals(taskId)))
          .write(const TasksCompanion(isActive: Value(false)));
    }
    
  6. Add getCompletionCount method to TasksDao:

    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;
    }
    
  7. Run dart run build_runner build --delete-conflicting-outputs to regenerate Drift code.

  8. 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
  1. 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
  2. In rooms_dao.dart watchRoomWithStats method, filter the task query to active-only:

    final taskList = await (select(tasks)
          ..where((t) => t.roomId.equals(room.id) & t.isActive.equals(true)))
        .get();
    
  3. Run dart run build_runner build --delete-conflicting-outputs to regenerate if needed.

  4. 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

<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>
After completion, create `.planning/phases/08-task-delete/08-01-SUMMARY.md`