--- phase: 07-task-sorting plan: 01 type: execute wave: 1 depends_on: [] files_modified: - lib/features/tasks/domain/task_sort_option.dart - lib/features/tasks/presentation/sort_preference_notifier.dart - lib/features/tasks/presentation/sort_preference_notifier.g.dart - lib/l10n/app_de.arb - lib/l10n/app_localizations.dart - lib/l10n/app_localizations_de.dart - lib/features/home/presentation/calendar_providers.dart - lib/features/tasks/presentation/task_providers.dart - test/features/tasks/presentation/sort_preference_notifier_test.dart autonomous: true requirements: [SORT-01, SORT-02, SORT-03] must_haves: truths: - "Sort preference persists across app restarts" - "CalendarDayList tasks are sorted according to the active sort preference" - "TaskListScreen tasks are sorted according to the active sort preference" - "Default sort is alphabetical (matches current CalendarDayList behavior)" artifacts: - path: "lib/features/tasks/domain/task_sort_option.dart" provides: "TaskSortOption enum with alphabetical, interval, effort values" exports: ["TaskSortOption"] - path: "lib/features/tasks/presentation/sort_preference_notifier.dart" provides: "SortPreferenceNotifier with SharedPreferences persistence" exports: ["SortPreferenceNotifier", "sortPreferenceProvider"] - path: "lib/features/home/presentation/calendar_providers.dart" provides: "calendarDayProvider sorts dayTasks by active sort preference" contains: "sortPreferenceProvider" - path: "lib/features/tasks/presentation/task_providers.dart" provides: "tasksInRoomProvider sorts tasks by active sort preference" contains: "sortPreferenceProvider" - path: "test/features/tasks/presentation/sort_preference_notifier_test.dart" provides: "Unit tests for sort preference persistence and default" key_links: - from: "lib/features/home/presentation/calendar_providers.dart" to: "sortPreferenceProvider" via: "ref.watch in calendarDayProvider" pattern: "ref\\.watch\\(sortPreferenceProvider\\)" - from: "lib/features/tasks/presentation/task_providers.dart" to: "sortPreferenceProvider" via: "ref.watch in tasksInRoomProvider" pattern: "ref\\.watch\\(sortPreferenceProvider\\)" --- Create the task sort domain model, SharedPreferences-backed persistence provider, and integrate sort logic into both task list providers (calendarDayProvider and tasksInRoomProvider). Purpose: Establishes the data layer and sort logic so that task lists react to sort preference changes. The UI plan (07-02) will add the dropdown widget that writes to this provider. Output: TaskSortOption enum, SortPreferenceNotifier, updated calendarDayProvider and tasksInRoomProvider with in-memory sorting, German localization strings for sort labels. @/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/07-task-sorting/07-CONTEXT.md From lib/features/tasks/domain/effort_level.dart: ```dart enum EffortLevel { low, // 0 medium, // 1 high, // 2 } ``` From lib/features/tasks/domain/frequency.dart: ```dart enum IntervalType { daily, // 0 everyNDays, // 1 weekly, // 2 biweekly, // 3 monthly, // 4 everyNMonths, // 5 quarterly, // 6 yearly, // 7 } ``` From lib/features/home/domain/daily_plan_models.dart: ```dart class TaskWithRoom { final Task task; final String roomName; final int roomId; } ``` From lib/features/home/domain/calendar_models.dart: ```dart class CalendarDayState { final DateTime selectedDate; final List dayTasks; final List overdueTasks; final int totalTaskCount; } ``` From lib/core/theme/theme_provider.dart (pattern to follow for SharedPreferences notifier): ```dart @riverpod class ThemeNotifier extends _$ThemeNotifier { @override ThemeMode build() { _loadPersistedThemeMode(); return ThemeMode.system; // sync default, async load overrides } Future _loadPersistedThemeMode() async { ... } Future setThemeMode(ThemeMode mode) async { state = mode; final prefs = await SharedPreferences.getInstance(); await prefs.setString(_themeModeKey, _themeModeToString(mode)); } } ``` From lib/features/home/presentation/calendar_providers.dart: ```dart final calendarDayProvider = StreamProvider.autoDispose((ref) { final db = ref.watch(appDatabaseProvider); final selectedDate = ref.watch(selectedDateProvider); // ... fetches dayTasks, overdueTasks, totalTaskCount // dayTasks come from watchTasksForDate which sorts alphabetically in SQL }); ``` From lib/features/tasks/presentation/task_providers.dart: ```dart final tasksInRoomProvider = StreamProvider.family.autoDispose, int>((ref, roomId) { final db = ref.watch(appDatabaseProvider); return db.tasksDao.watchTasksInRoom(roomId); // watchTasksInRoom sorts by nextDueDate in SQL }); ``` From lib/core/database/database.dart (Task table columns relevant to sorting): ```dart class Tasks extends Table { TextColumn get name => text().withLength(min: 1, max: 200)(); IntColumn get intervalType => intEnum()(); IntColumn get intervalDays => integer().withDefault(const Constant(1))(); IntColumn get effortLevel => intEnum()(); } ``` Task 1: Create TaskSortOption enum, SortPreferenceNotifier, and localization strings lib/features/tasks/domain/task_sort_option.dart, lib/features/tasks/presentation/sort_preference_notifier.dart, lib/features/tasks/presentation/sort_preference_notifier.g.dart, lib/l10n/app_de.arb, lib/l10n/app_localizations.dart, lib/l10n/app_localizations_de.dart, test/features/tasks/presentation/sort_preference_notifier_test.dart - Default sort preference is TaskSortOption.alphabetical - setSortOption(TaskSortOption.interval) updates state to interval - Sort preference persists: after setSortOption(effort), a fresh notifier reads back effort from SharedPreferences - TaskSortOption enum has exactly 3 values: alphabetical, interval, effort 1. Create `lib/features/tasks/domain/task_sort_option.dart`: - `enum TaskSortOption { alphabetical, interval, effort }` — three values only, no index stability concern since this is NOT stored as intEnum in drift (stored as string in SharedPreferences) 2. Create `lib/features/tasks/presentation/sort_preference_notifier.dart`: - Follow the exact ThemeNotifier pattern from `lib/core/theme/theme_provider.dart` - `@riverpod class SortPreferenceNotifier extends _$SortPreferenceNotifier` - `build()` returns `TaskSortOption.alphabetical` synchronously (default = alphabetical per user decision for continuity with current A-Z sort in CalendarDayList), then calls `_loadPersisted()` async - `_loadPersisted()` reads `SharedPreferences.getString('task_sort_option')` and maps to enum - `setSortOption(TaskSortOption option)` sets state immediately then persists string to SharedPreferences - Static helpers `_fromString` / `_toString` for serialization (use enum .name property) - The generated provider will be named `sortPreferenceProvider` (Riverpod 3 naming convention, consistent with themeProvider) 3. Run `dart run build_runner build --delete-conflicting-outputs` to generate `.g.dart` 4. Add localization strings to `lib/l10n/app_de.arb`: - `"sortAlphabetical": "A\u2013Z"` (A-Z with en-dash, concise label per user decision) - `"sortInterval": "Intervall"` (German for interval/frequency) - `"sortEffort": "Aufwand"` (German for effort, matches existing taskFormEffortLabel context) - `"sortLabel": "Sortierung"` (label for accessibility/semantics on the dropdown) 5. Run `flutter gen-l10n` to regenerate localization files 6. Write tests in `test/features/tasks/presentation/sort_preference_notifier_test.dart`: - Follow the pattern from notification_settings test: `makeContainer()` helper that creates ProviderContainer, awaits `Future.delayed(Duration.zero)` for async load - `SharedPreferences.setMockInitialValues({})` in setUp - Test: default is alphabetical - Test: setSortOption updates state - Test: persisted value is loaded on restart (set mock initial values with key, verify state after load) cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/presentation/sort_preference_notifier_test.dart -x && flutter analyze --no-fatal-infos TaskSortOption enum exists with 3 values. SortPreferenceNotifier persists to SharedPreferences. 3+ unit tests pass. ARB file has 4 new sort strings. dart analyze clean. Task 2: Integrate sort logic into calendarDayProvider and tasksInRoomProvider lib/features/home/presentation/calendar_providers.dart, lib/features/tasks/presentation/task_providers.dart 1. Edit `lib/features/home/presentation/calendar_providers.dart`: - Add import for `sort_preference_notifier.dart` and `task_sort_option.dart` - Inside `calendarDayProvider`, add `final sortOption = ref.watch(sortPreferenceProvider);` - After constructing `CalendarDayState`, apply in-memory sort to `dayTasks` list before returning. Do NOT sort overdueTasks (overdue section stays pinned at top in its existing order per user discretion decision). - Sort implementation — create a top-level helper function `List _sortTasks(List tasks, TaskSortOption sortOption)` that returns a new sorted list: - `alphabetical`: sort by `task.name.toLowerCase()` (case-insensitive A-Z) - `interval`: sort by `task.intervalType.index` ascending (daily=0 is most frequent, yearly=7 is least), then by `task.intervalDays` ascending as tiebreaker - `effort`: sort by `task.effortLevel.index` ascending (low=0, medium=1, high=2) - Apply: `dayTasks: _sortTasks(dayTasks, sortOption)` in the CalendarDayState constructor call - Note: The SQL `orderBy([OrderingTerm.asc(tasks.name)])` in CalendarDao.watchTasksForDate still runs, but the in-memory sort overrides it. This is intentional — the SQL sort provides a stable baseline, the in-memory sort applies the user's preference. 2. Edit `lib/features/tasks/presentation/task_providers.dart`: - Add import for `sort_preference_notifier.dart` and `task_sort_option.dart` - In `tasksInRoomProvider`, add `final sortOption = ref.watch(sortPreferenceProvider);` - Map the stream to apply in-memory sorting: `return db.tasksDao.watchTasksInRoom(roomId).map((tasks) => _sortTasksRaw(tasks, sortOption));` - Create a top-level helper `List _sortTasksRaw(List tasks, TaskSortOption sortOption)` that sorts raw Task objects (not TaskWithRoom): - `alphabetical`: sort by `task.name.toLowerCase()` - `interval`: sort by `task.intervalType.index`, then `task.intervalDays` - `effort`: sort by `task.effortLevel.index` - Returns a new sorted list (do not mutate the original) 3. Verify both providers react to sort preference changes by running existing tests (they should still pass since default sort is alphabetical and current data is already alphabetically sorted or test data is single-item). cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test && flutter analyze --no-fatal-infos calendarDayProvider watches sortPreferenceProvider and sorts dayTasks accordingly. tasksInRoomProvider watches sortPreferenceProvider and sorts tasks accordingly. All 106+ existing tests pass. dart analyze clean. - `flutter test` — all 106+ tests pass (existing + new sort preference tests) - `flutter analyze --no-fatal-infos` — zero issues - `sortPreferenceProvider` is watchable and defaults to alphabetical - Both calendarDayProvider and tasksInRoomProvider react to sort preference changes - TaskSortOption enum exists with alphabetical, interval, effort values - SortPreferenceNotifier persists sort preference to SharedPreferences - Default sort is alphabetical (continuity with existing A-Z sort) - calendarDayProvider sorts dayTasks by active sort (overdue section unsorted) - tasksInRoomProvider sorts tasks by active sort - All tests pass, analyze clean After completion, create `.planning/phases/07-task-sorting/07-01-SUMMARY.md`