docs(02): create phase plan — 5 plans in 4 waves covering rooms, tasks, templates, and verification
This commit is contained in:
336
.planning/phases/02-rooms-and-tasks/02-01-PLAN.md
Normal file
336
.planning/phases/02-rooms-and-tasks/02-01-PLAN.md
Normal file
@@ -0,0 +1,336 @@
|
||||
---
|
||||
phase: 02-rooms-and-tasks
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- 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
|
||||
autonomous: true
|
||||
requirements:
|
||||
- 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
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "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"
|
||||
artifacts:
|
||||
- path: "lib/core/database/database.dart"
|
||||
provides: "Rooms, Tasks, TaskCompletions table definitions, schema v2 migration, foreign keys PRAGMA"
|
||||
contains: "schemaVersion => 2"
|
||||
- path: "lib/features/rooms/data/rooms_dao.dart"
|
||||
provides: "Room CRUD + stream queries + reorder + cascade delete"
|
||||
exports: ["RoomsDao"]
|
||||
- path: "lib/features/tasks/data/tasks_dao.dart"
|
||||
provides: "Task CRUD + stream queries + completion transaction"
|
||||
exports: ["TasksDao"]
|
||||
- path: "lib/features/tasks/domain/scheduling.dart"
|
||||
provides: "calculateNextDueDate, catchUpToPresent pure functions"
|
||||
exports: ["calculateNextDueDate", "catchUpToPresent"]
|
||||
- path: "lib/features/tasks/domain/frequency.dart"
|
||||
provides: "IntervalType enum, FrequencyInterval model"
|
||||
exports: ["IntervalType", "FrequencyInterval"]
|
||||
- path: "lib/features/tasks/domain/effort_level.dart"
|
||||
provides: "EffortLevel enum (low, medium, high)"
|
||||
exports: ["EffortLevel"]
|
||||
- path: "lib/features/tasks/domain/relative_date.dart"
|
||||
provides: "formatRelativeDate German formatter"
|
||||
exports: ["formatRelativeDate"]
|
||||
- path: "lib/features/rooms/domain/room_icons.dart"
|
||||
provides: "Curated list of ~25 household Material Icons with name+IconData pairs"
|
||||
exports: ["curatedRoomIcons", "mapIconName"]
|
||||
- path: "lib/features/templates/data/task_templates.dart"
|
||||
provides: "Static Dart constant template data for all 14 room types"
|
||||
exports: ["TaskTemplate", "roomTemplates", "detectRoomType"]
|
||||
key_links:
|
||||
- from: "lib/features/tasks/data/tasks_dao.dart"
|
||||
to: "lib/features/tasks/domain/scheduling.dart"
|
||||
via: "completeTask calls calculateNextDueDate + catchUpToPresent"
|
||||
pattern: "calculateNextDueDate|catchUpToPresent"
|
||||
- from: "lib/core/database/database.dart"
|
||||
to: "lib/features/rooms/data/rooms_dao.dart"
|
||||
via: "database registers RoomsDao"
|
||||
pattern: "daos.*RoomsDao"
|
||||
- from: "lib/core/database/database.dart"
|
||||
to: "lib/features/tasks/data/tasks_dao.dart"
|
||||
via: "database registers TasksDao"
|
||||
pattern: "daos.*TasksDao"
|
||||
- from: "lib/features/tasks/domain/frequency.dart"
|
||||
to: "lib/core/database/database.dart"
|
||||
via: "IntervalType enum used as intEnum column in Tasks table"
|
||||
pattern: "intEnum.*IntervalType"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</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/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
|
||||
|
||||
<interfaces>
|
||||
<!-- Existing code contracts the executor needs -->
|
||||
|
||||
From lib/core/database/database.dart:
|
||||
```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:
|
||||
```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):
|
||||
```dart
|
||||
@riverpod
|
||||
class ThemeNotifier extends _$ThemeNotifier {
|
||||
@override
|
||||
ThemeMode build() { ... }
|
||||
}
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Define Drift tables, migration, DAOs, and domain models</name>
|
||||
<files>
|
||||
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
|
||||
</files>
|
||||
<behavior>
|
||||
--- 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
|
||||
</behavior>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<done>
|
||||
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.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Create task template data and template tests</name>
|
||||
<files>
|
||||
lib/features/templates/data/task_templates.dart,
|
||||
test/features/templates/task_templates_test.dart
|
||||
</files>
|
||||
<behavior>
|
||||
- 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
|
||||
</behavior>
|
||||
<action>
|
||||
**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<String, List<TaskTemplate>>` 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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/templates/task_templates_test.dart</automated>
|
||||
</verify>
|
||||
<done>
|
||||
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.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
```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.
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-rooms-and-tasks/02-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user