Files
HouseHoldKeaper/.planning/phases/02-rooms-and-tasks/02-01-PLAN.md

20 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
02-rooms-and-tasks 01 execute 1
lib/core/database/database.dart
lib/features/rooms/data/rooms_dao.dart
lib/features/tasks/data/tasks_dao.dart
lib/features/tasks/domain/scheduling.dart
lib/features/tasks/domain/frequency.dart
lib/features/tasks/domain/effort_level.dart
lib/features/tasks/domain/relative_date.dart
lib/features/rooms/domain/room_icons.dart
lib/features/templates/data/task_templates.dart
test/features/rooms/data/rooms_dao_test.dart
test/features/tasks/data/tasks_dao_test.dart
test/features/tasks/domain/scheduling_test.dart
test/features/templates/task_templates_test.dart
true
ROOM-01
ROOM-02
ROOM-03
ROOM-04
ROOM-05
TASK-01
TASK-02
TASK-03
TASK-04
TASK-05
TASK-06
TASK-07
TASK-08
TMPL-01
TMPL-02
truths artifacts key_links
Room CRUD operations (insert, update, delete with cascade, reorder) work correctly at the database layer
Task CRUD operations (insert, update, delete, sorted by due date) work correctly at the database layer
Task completion records a timestamp and auto-calculates next due date using the scheduling utility
All 11 preset frequency intervals and custom intervals produce correct next due dates
Calendar-anchored intervals clamp to last day of month with anchor memory
Catch-up logic advances past-due dates to the next future occurrence
All 14 room types have bundled German-language task templates
Overdue detection correctly identifies tasks with nextDueDate before today
path provides contains
lib/core/database/database.dart Rooms, Tasks, TaskCompletions table definitions, schema v2 migration, foreign keys PRAGMA schemaVersion => 2
path provides exports
lib/features/rooms/data/rooms_dao.dart Room CRUD + stream queries + reorder + cascade delete
RoomsDao
path provides exports
lib/features/tasks/data/tasks_dao.dart Task CRUD + stream queries + completion transaction
TasksDao
path provides exports
lib/features/tasks/domain/scheduling.dart calculateNextDueDate, catchUpToPresent pure functions
calculateNextDueDate
catchUpToPresent
path provides exports
lib/features/tasks/domain/frequency.dart IntervalType enum, FrequencyInterval model
IntervalType
FrequencyInterval
path provides exports
lib/features/tasks/domain/effort_level.dart EffortLevel enum (low, medium, high)
EffortLevel
path provides exports
lib/features/tasks/domain/relative_date.dart formatRelativeDate German formatter
formatRelativeDate
path provides exports
lib/features/rooms/domain/room_icons.dart Curated list of ~25 household Material Icons with name+IconData pairs
curatedRoomIcons
mapIconName
path provides exports
lib/features/templates/data/task_templates.dart Static Dart constant template data for all 14 room types
TaskTemplate
roomTemplates
detectRoomType
from to via pattern
lib/features/tasks/data/tasks_dao.dart lib/features/tasks/domain/scheduling.dart completeTask calls calculateNextDueDate + catchUpToPresent calculateNextDueDate|catchUpToPresent
from to via pattern
lib/core/database/database.dart lib/features/rooms/data/rooms_dao.dart database registers RoomsDao daos.*RoomsDao
from to via pattern
lib/core/database/database.dart lib/features/tasks/data/tasks_dao.dart database registers TasksDao daos.*TasksDao
from to via pattern
lib/features/tasks/domain/frequency.dart lib/core/database/database.dart IntervalType enum used as intEnum column in Tasks table intEnum.*IntervalType
Build the complete data layer for rooms and tasks: Drift tables with schema v2 migration, DAOs with stream queries and cascade operations, pure scheduling utility, domain enums/models, template data, and comprehensive unit tests.

Purpose: This is the foundation everything else in Phase 2 builds on. All UI plans (rooms, tasks, templates) depend on having working DAOs, domain models, and tested scheduling logic. Output: Database schema v2 with Rooms/Tasks/TaskCompletions tables, two DAOs, scheduling utility, template data, and passing unit test suite.

<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/02-rooms-and-tasks/2-CONTEXT.md @.planning/phases/02-rooms-and-tasks/02-RESEARCH.md @.planning/phases/02-rooms-and-tasks/02-VALIDATION.md

From lib/core/database/database.dart:

@DriftDatabase(tables: [])
class AppDatabase extends _$AppDatabase {
  AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
  @override
  int get schemaVersion => 1;
}

From lib/core/providers/database_provider.dart:

@Riverpod(keepAlive: true)
AppDatabase appDatabase(Ref ref) {
  final db = AppDatabase();
  ref.onDispose(db.close);
  return db;
}

From lib/core/theme/theme_provider.dart (pattern reference for AsyncNotifier):

@riverpod
class ThemeNotifier extends _$ThemeNotifier {
  @override
  ThemeMode build() { ... }
}
Task 1: Define Drift tables, migration, DAOs, and domain models lib/features/tasks/domain/frequency.dart, lib/features/tasks/domain/effort_level.dart, lib/features/tasks/domain/scheduling.dart, lib/features/tasks/domain/relative_date.dart, lib/features/rooms/domain/room_icons.dart, lib/core/database/database.dart, lib/features/rooms/data/rooms_dao.dart, lib/features/tasks/data/tasks_dao.dart, test/features/rooms/data/rooms_dao_test.dart, test/features/tasks/data/tasks_dao_test.dart, test/features/tasks/domain/scheduling_test.dart --- Scheduling (scheduling_test.dart) --- - calculateNextDueDate: daily adds 1 day - calculateNextDueDate: everyNDays(3) adds 3 days - calculateNextDueDate: weekly adds 7 days - calculateNextDueDate: biweekly adds 14 days - calculateNextDueDate: monthly from Jan 15 gives Feb 15 - calculateNextDueDate: monthly from Jan 31 gives Feb 28 (clamping) - calculateNextDueDate: monthly from Feb 28 (anchor 31) gives Mar 31 (anchor memory) - calculateNextDueDate: quarterly from Jan 15 gives Apr 15 - calculateNextDueDate: yearly from 2026-03-15 gives 2027-03-15 - calculateNextDueDate: everyNMonths(2) adds 2 months - catchUpToPresent: skips past occurrences to reach today or future - catchUpToPresent: returns date unchanged if already in future - formatRelativeDate: today returns "Heute" - formatRelativeDate: tomorrow returns "Morgen" - formatRelativeDate: 3 days from now returns "in 3 Tagen" - formatRelativeDate: 1 day overdue returns "Uberfaellig seit 1 Tag" - formatRelativeDate: 5 days overdue returns "Uberfaellig seit 5 Tagen" --- RoomsDao (rooms_dao_test.dart) --- - insertRoom returns a valid id - watchAllRooms emits rooms ordered by sortOrder - updateRoom changes name and iconName - deleteRoom cascades to associated tasks and completions - reorderRooms updates sortOrder for all rooms - watchRoomWithStats emits room with due task count and cleanliness ratio --- TasksDao (tasks_dao_test.dart) --- - insertTask returns a valid id - watchTasksInRoom emits tasks sorted by nextDueDate ascending - updateTask changes name, description, interval, effort - deleteTask removes the task - completeTask records completion and updates nextDueDate - completeTask with overdue task catches up to present - tasks with nextDueDate before today are detected as overdue 1. Create domain models FIRST (these are the contracts):
**lib/features/tasks/domain/frequency.dart**: Define `IntervalType` enum with values in this EXACT order (for intEnum stability): `daily, everyNDays, weekly, biweekly, monthly, everyNMonths, quarterly, yearly`. Add index comments. Create `FrequencyInterval` class with `intervalType` and `days` fields, plus a `label()` method returning German display strings ("Taeglich", "Alle 2 Tage", "Woechentlich", "Alle 2 Wochen", "Monatlich", "Alle 2 Monate", "Vierteljaehrlich", "Halbjaehrlich", "Jaehrlich", "Alle N Tage/Wochen/Monate" for custom). Include the full preset list as a static const list per TASK-04: daily, every 2 days, every 3 days, weekly, biweekly, monthly, every 2 months, quarterly, every 6 months, yearly.

**lib/features/tasks/domain/effort_level.dart**: Define `EffortLevel` enum: `low, medium, high` (this exact order, with index comments). Add `label()` extension returning German: "Gering", "Mittel", "Hoch".

**lib/features/tasks/domain/scheduling.dart**: Implement `calculateNextDueDate()` and `catchUpToPresent()` as top-level pure functions per the RESEARCH.md Pattern 5. Accept `DateTime today` parameter (not `DateTime.now()`) for testability. Day-count intervals use `Duration(days: N)`. Calendar-anchored intervals use `_addMonths()` helper with anchor day clamping. Catch-up uses a while loop advancing until `nextDue >= today`.

**lib/features/tasks/domain/relative_date.dart**: Implement `formatRelativeDate(DateTime dueDate, DateTime today)` returning German strings: "Heute", "Morgen", "in X Tagen", "Uberfaellig seit 1 Tag", "Uberfaellig seit X Tagen". Accept `today` parameter for testability.

**lib/features/rooms/domain/room_icons.dart**: Define `curatedRoomIcons` as a const list of records `({String name, IconData icon})` with ~25 household Material Icons (kitchen, bathtub, bed, living, weekend, door_front_door, desk, garage, balcony, local_laundry_service, stairs, child_care, single_bed, dining, yard, grass, home, inventory_2, window, cleaning_services, iron, microwave, shower, chair, door_sliding). Add `IconData mapIconName(String name)` function that maps stored string names back to IconData.

2. Update database with tables and migration:

**lib/core/database/database.dart**: Add three Drift table classes (`Rooms`, `Tasks`, `TaskCompletions`) above the database class. Import `IntervalType` and `EffortLevel` for `intEnum` columns. Register tables and DAOs in `@DriftDatabase` annotation. Increment `schemaVersion` to 2. Add `MigrationStrategy` with `onCreate` calling `m.createAll()`, `onUpgrade` that creates all three tables when upgrading from v1, and `beforeOpen` that runs `PRAGMA foreign_keys = ON`.

Table columns per RESEARCH.md Pattern 1:
- Rooms: id (autoIncrement), name (text 1-100), iconName (text), sortOrder (int, default 0), createdAt (dateTime clientDefault)
- Tasks: id (autoIncrement), roomId (int, references Rooms#id), name (text 1-200), description (text nullable), intervalType (intEnum<IntervalType>), intervalDays (int, default 1), anchorDay (int nullable), effortLevel (intEnum<EffortLevel>), nextDueDate (dateTime), createdAt (dateTime clientDefault)
- TaskCompletions: id (autoIncrement), taskId (int, references Tasks#id), completedAt (dateTime)

3. Create DAOs:

**lib/features/rooms/data/rooms_dao.dart**: `@DriftAccessor(tables: [Rooms, Tasks, TaskCompletions])` with:
- `watchAllRooms()` -> `Stream<List<Room>>` ordered by sortOrder
- `watchRoomWithStats()` -> `Stream<List<RoomWithStats>>` joining rooms with task counts and cleanliness ratio. Create a `RoomWithStats` class (room, totalTasks, dueTasks, overdueCount, cleanlinessRatio). The cleanliness ratio = (totalTasks - overdueCount) / totalTasks (1.0 when no overdue, 0.0 when all overdue). Use `customSelect` or join query to compute these counts.
- `insertRoom(RoomsCompanion)` -> `Future<int>`
- `updateRoom(Room)` -> `Future<bool>`
- `deleteRoom(int roomId)` -> `Future<void>` with transaction: delete completions for room's tasks, delete tasks, delete room
- `reorderRooms(List<int> roomIds)` -> `Future<void>` with transaction updating sortOrder
- `getRoomById(int id)` -> `Future<Room>`

**lib/features/tasks/data/tasks_dao.dart**: `@DriftAccessor(tables: [Tasks, TaskCompletions])` with:
- `watchTasksInRoom(int roomId)` -> `Stream<List<Task>>` ordered by nextDueDate ASC
- `insertTask(TasksCompanion)` -> `Future<int>`
- `updateTask(Task)` -> `Future<bool>`
- `deleteTask(int taskId)` -> `Future<void>` (delete completions first, then task)
- `completeTask(int taskId, {DateTime? now})` -> `Future<void>` with transaction: get task, insert completion, calculate next due via `calculateNextDueDate`, catch up via `catchUpToPresent`, update task's nextDueDate. Accept optional `now` for testability (defaults to `DateTime.now()`).
- `getOverdueTaskCount(int roomId, {DateTime? today})` -> `Future<int>`

4. Run `dart run build_runner build --delete-conflicting-outputs` to generate all .g.dart files.

5. Run `dart run drift_dev make-migrations` to capture drift_schema_v2.json.

6. Write unit tests (RED phase first, then make GREEN):

**test/features/tasks/domain/scheduling_test.dart**: Test all behaviors listed above for calculateNextDueDate and catchUpToPresent and formatRelativeDate. Use fixed dates (no DateTime.now()).

**test/features/rooms/data/rooms_dao_test.dart**: Use `AppDatabase(NativeDatabase.memory())` pattern from Phase 1. Test insert, watchAll, update, delete with cascade, reorder, watchRoomWithStats.

**test/features/tasks/data/tasks_dao_test.dart**: Same in-memory DB pattern. Test insert, watchTasksInRoom (verify sort order), update, delete, completeTask (verify completion record + nextDueDate update), overdue detection.

7. Run `flutter test` to verify all tests pass.
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/domain/scheduling_test.dart test/features/rooms/data/rooms_dao_test.dart test/features/tasks/data/tasks_dao_test.dart All domain models defined with correct enum ordering. Drift database at schema v2 with Rooms, Tasks, TaskCompletions tables. Foreign keys enabled via PRAGMA. RoomsDao and TasksDao with full CRUD + stream queries + cascade delete + completion transaction. Scheduling utility correctly handles all 8 interval types, anchor memory, and catch-up logic. All unit tests passing. Task 2: Create task template data and template tests lib/features/templates/data/task_templates.dart, test/features/templates/task_templates_test.dart - roomTemplates map contains exactly 14 room type keys - Each room type key maps to a non-empty list of TaskTemplate objects - Every TaskTemplate has a non-empty German name - Every TaskTemplate has a valid IntervalType - Every TaskTemplate has a valid EffortLevel - detectRoomType("Kueche") returns "kueche" - detectRoomType("Mein Badezimmer") returns "badezimmer" - detectRoomType("Bad") returns "badezimmer" (alias) - detectRoomType("Random Name") returns null - detectRoomType is case-insensitive - All 14 room types from TMPL-02 are present: kueche, badezimmer, schlafzimmer, wohnzimmer, flur, buero, garage, balkon, waschkueche, keller, kinderzimmer, gaestezimmer, esszimmer, garten **lib/features/templates/data/task_templates.dart**: Create `TaskTemplate` class with const constructor (name, description nullable, intervalType, intervalDays defaults to 1, effortLevel). Create `roomTemplates` as `const Map>` with 4-6 templates per room type for all 14 types. Use realistic German household task names with appropriate frequencies and effort levels.
Room types and sample tasks:
- kueche: Abspuelen (daily/low), Herd reinigen (weekly/medium), Kuehlschrank reinigen (monthly/medium), Backofen reinigen (monthly/high), Muell rausbringen (everyNDays 2/low)
- badezimmer: Toilette putzen (weekly/medium), Spiegel reinigen (weekly/low), Dusche reinigen (weekly/medium), Waschbecken reinigen (weekly/low), Badezimmerboden wischen (weekly/medium)
- schlafzimmer: Bettwasche wechseln (biweekly/medium), Staubsaugen (weekly/medium), Staub wischen (weekly/low), Fenster putzen (monthly/medium)
- wohnzimmer: Staubsaugen (weekly/medium), Staub wischen (weekly/low), Kissen aufschuetteln (daily/low), Fenster putzen (monthly/medium)
- flur: Boden wischen (weekly/medium), Schuhe aufraumen (weekly/low), Garderobe aufraeumen (monthly/low)
- buero: Schreibtisch aufraeumen (weekly/low), Staubsaugen (weekly/medium), Bildschirm reinigen (monthly/low), Papierkorb leeren (weekly/low)
- garage: Boden fegen (monthly/medium), Aufraeumen (quarterly/high), Werkzeug sortieren (quarterly/medium)
- balkon: Boden fegen (weekly/low), Pflanzen giessen (everyNDays 2/low), Gelaender reinigen (monthly/medium), Moebel reinigen (monthly/medium)
- waschkueche: Waschmaschine reinigen (monthly/medium), Trockner reinigen (monthly/medium), Boden wischen (weekly/medium), Waschmittel auffuellen (monthly/low)
- keller: Staubsaugen (monthly/medium), Aufraeumen (quarterly/high), Luftentfeuchter pruefen (monthly/low)
- kinderzimmer: Spielzeug aufraeumen (daily/low), Staubsaugen (weekly/medium), Bettwasche wechseln (biweekly/medium), Staub wischen (weekly/low)
- gaestezimmer: Staubsaugen (biweekly/medium), Bettwasche wechseln (monthly/medium), Staub wischen (biweekly/low), Lueften (weekly/low)
- esszimmer: Tisch abwischen (daily/low), Staubsaugen (weekly/medium), Stuehle reinigen (monthly/medium)
- garten: Rasen maehen (weekly/high), Unkraut jaeten (weekly/medium), Hecke schneiden (quarterly/high), Laub harken (weekly/medium in autumn context), Pflanzen giessen (everyNDays 2/low)

Create `detectRoomType(String roomName)` function: lowercase + trim the input, check if it contains any room type key or its aliases. Aliases map: badezimmer -> [bad, wc, toilette], buero -> [arbeitszimmer, office], waschkueche -> [waschraum], garten -> [terrasse, aussenbereich, hof], gaestezimmer -> [gaeste], kinderzimmer -> [kinder], schlafzimmer -> [schlafraum], kueche -> [kitchen], esszimmer -> [essbereich].

**test/features/templates/task_templates_test.dart**: Test all behaviors listed above. Verify exact room type count (14), non-empty template lists, valid fields on every template, detectRoomType matching and null cases.
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/templates/task_templates_test.dart All 14 room types have 3-6 German-language task templates with valid names, intervals, and effort levels. detectRoomType correctly identifies room types from names and aliases. Template test suite passing. ```bash cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/rooms/ test/features/tasks/ test/features/templates/ && flutter test ``` All Phase 2 data layer tests pass. Full existing test suite (Phase 1 + Phase 2) passes. `dart analyze` shows no new errors.

<success_criteria>

  • Database schema v2 with Rooms, Tasks, TaskCompletions tables
  • RoomsDao: CRUD + watchAll + watchWithStats + cascade delete + reorder
  • TasksDao: CRUD + watchInRoom (sorted by due date) + completeTask transaction + overdue detection
  • Scheduling utility: all 8 interval types, anchor memory, catch-up logic
  • Domain models: IntervalType, EffortLevel, FrequencyInterval, curatedRoomIcons, formatRelativeDate
  • Template data: 14 room types with German task templates, detectRoomType
  • All unit tests green </success_criteria>
After completion, create `.planning/phases/02-rooms-and-tasks/02-01-SUMMARY.md`