85 Commits

Author SHA1 Message Date
0bf32ae1ad refactor(database): remove unused table mapping and data classes
Some checks failed
CI / ci (push) Has been cancelled
Build and Release to F-Droid / ci (push) Successful in 11m4s
Build and Release to F-Droid / build-and-deploy (push) Successful in 12m9s
- Deleted table `map` methods, data classes, and companions in schema verification code.
- Updated migrations for more precise version checks.
2026-03-19 15:27:03 +01:00
22a0f2f99b docs(phase-10): complete phase execution
Some checks failed
CI / ci (push) Has been cancelled
Build and Release to F-Droid / build-and-deploy (push) Has been cancelled
Build and Release to F-Droid / ci (push) Has been cancelled
2026-03-19 08:29:15 +01:00
35905af70c docs(10-01): add self-check and final commit hash to SUMMARY.md 2026-03-19 08:25:57 +01:00
80e701187e docs(10-01): complete dead-code-cleanup plan
- Create 10-01-SUMMARY.md: deleted 3 orphaned v1.0 daily plan files, removed DailyPlanState
- Update STATE.md: position, decisions, blocker resolved, session info
- Update ROADMAP.md: Phase 10 complete, 10-01-PLAN.md checked off
- Update REQUIREMENTS.md: CLN-01 marked complete
2026-03-19 08:25:13 +01:00
510529a950 chore(10-01): delete orphaned v1.0 daily plan presentation files and remove DailyPlanState
- Delete daily_plan_providers.dart (orphaned since Phase 5 calendar strip)
- Delete daily_plan_task_row.dart (orphaned since Phase 5 calendar strip)
- Delete progress_card.dart (orphaned since Phase 5 calendar strip)
- Remove DailyPlanState class from daily_plan_models.dart (only used by deleted files)
- Preserve TaskWithRoom class (still used by calendar system)
2026-03-19 08:21:38 +01:00
11c70f63ae docs(10-dead-code-cleanup): create phase plan 2026-03-19 08:17:25 +01:00
d83e6332cd docs(phase-09): complete phase execution 2026-03-18 22:51:10 +01:00
8af0b1b4e5 docs(09-01): complete frequency picker rework plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:47:04 +01:00
8a0b69b688 feat(09-01): rework frequency picker with shortcut chips and freeform picker
- Replace 10-chip grid + hidden Custom mode with 4 shortcut chips (Täglich, Wöchentlich, Alle 2 Wochen, Monatlich)
- Always-visible freeform 'Alle [N] [Tage/Wochen/Monate]' picker row below chips
- Bidirectional sync: tapping chip populates picker; editing picker recalculates chip highlight
- _resolveFrequency() now reads exclusively from picker (single source of truth)
- Edit mode correctly loads all 8 IntervalType values including quarterly and yearly
- Add l10n keys frequencyShortcutDaily/Weekly/Biweekly/Monthly to app_de.arb
2026-03-18 22:45:38 +01:00
1fd6c05f0f docs(09): complete phase planning 2026-03-18 22:42:23 +01:00
c482f16b8d docs(09-task-creation-ux): create phase plan 2026-03-18 22:40:38 +01:00
8a3fb65e20 docs(phase-08): complete phase execution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 21:08:28 +01:00
6db4611719 docs(08-02): complete task delete UI plan
- Add 08-02-SUMMARY.md with delete button and smartDeleteTask outcomes
- Update STATE.md with progress, decisions, and session continuity
- Update ROADMAP.md: phase 08-task-delete 2/2 plans complete
- Mark DEL-01 and DEL-04 requirements complete
2026-03-18 21:03:21 +01:00
6133c977f5 feat(08-02): add delete button and confirmation dialog to TaskFormScreen
- Red FilledButton.icon with error color below history section (edit mode only)
- _onDelete shows AlertDialog with taskDeleteConfirmTitle/Message l10n strings
- Confirm calls smartDeleteTask and pops back to room task list
- Cancel dismisses dialog with no action
- Button disabled while _isLoading
- All 144 tests pass, dart analyze clean
2026-03-18 21:01:53 +01:00
1b1b981dac feat(08-02): add smartDeleteTask to TaskActions provider
- Checks completion count before deleting
- Hard-deletes tasks with 0 completions
- Soft-deletes (isActive=false) tasks with completions
- Keeps existing deleteTask method for cascade/other uses
2026-03-18 21:00:29 +01:00
3bfa411d29 docs(08-01): complete isActive column and DAO filtering plan
- Create 08-01-SUMMARY.md with execution results and deviation docs
- Update STATE.md: progress 50%, decisions recorded, session updated
- Update ROADMAP.md: phase 8 marked In Progress (1/2 plans complete)
- Mark requirements DEL-02 and DEL-03 complete in REQUIREMENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 20:58:44 +01:00
b2f14dcd97 feat(08-01): add isActive filters to CalendarDao, DailyPlanDao, RoomsDao
- CalendarDao: filter all 6 task queries (watchTasksForDate,
  watchTasksForDateInRoom, watchOverdueTasks, watchOverdueTasksInRoom,
  getTaskCount, getTaskCountInRoom) by isActive=true
- DailyPlanDao: filter all 3 queries (watchAllTasksWithRoomName,
  getOverdueAndTodayTaskCount, getOverdueTaskCount) by isActive=true
- RoomsDao: filter watchRoomWithStats task query by isActive=true
- Update migration test: add schema_v3.dart, test v1->v3 and v2->v3 paths
- Update database_test schemaVersion assertion to expect 3
- Fix test helpers in home_screen_test and task_list_screen_test to pass isActive=true
2026-03-18 20:56:34 +01:00
4b51f5fa04 feat(08-01): add isActive column, migration v3, softDeleteTask and getCompletionCount
- Add isActive BoolColumn (default true) to Tasks table
- Bump schema version from 2 to 3 with addColumn migration
- Filter watchTasksInRoom to isActive=true only
- Filter getOverdueTaskCount to isActive=true only
- Add softDeleteTask(taskId) - sets isActive=false without removing data
- Add getCompletionCount(taskId) - counts TaskCompletions for a task
2026-03-18 20:49:45 +01:00
a2cef91d7e test(08-01): add failing tests for softDeleteTask, getCompletionCount, isActive filtering
- Tests for softDeleteTask sets isActive=false without removing from DB
- Tests for getCompletionCount with 0 and N completions
- Tests for watchTasksInRoom excludes inactive tasks
- Tests for getOverdueTaskCount excludes inactive tasks
- Test for hard deleteTask still removes task and completions
2026-03-18 20:48:09 +01:00
cff5f9e67b docs(08-task-delete): create phase plan 2026-03-18 20:45:08 +01:00
5fb688fc22 docs(state): record phase 8 context session 2026-03-18 20:40:19 +01:00
aed676c236 docs(08): capture phase context 2026-03-18 20:40:09 +01:00
b00ed8fac1 docs: start milestone v1.2 Polish & Task Management
Define requirements (9 total: task delete, creation UX, cleanup),
create roadmap (phases 8-10), and update PROJECT.md with new
milestone goals. Room cover photos dropped from scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 20:24:12 +01:00
1f59e2ef8e chore: release 1.1.5
All checks were successful
CI / ci (push) Successful in 10m56s
Build and Release to F-Droid / ci (push) Successful in 10m50s
Build and Release to F-Droid / build-and-deploy (push) Successful in 12m8s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 12:37:19 +01:00
de6f5a6784 fix(ci): remove dart pub audit step (unsupported in runner SDK)
All checks were successful
CI / ci (push) Successful in 10m50s
The stable Flutter SDK on the CI runner does not include
the `dart pub audit` subcommand. Trivy scan still covers
dependency security.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 12:06:19 +01:00
3d28aba0db fix(ci): install jq before flutter-action in CI and release workflows
Some checks failed
CI / ci (push) Failing after 3m33s
subosito/flutter-action@v2 requires jq to parse action inputs.
The ci job in both workflows was missing the install step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:51:32 +01:00
92de2bd7de feat: add F-Droid store icon and CI workflow
Some checks failed
Build and Release to F-Droid / build-and-deploy (push) Has been cancelled
Build and Release to F-Droid / ci (push) Has been cancelled
CI / ci (push) Failing after 1m2s
Add 512x512 app icon to F-Droid metadata for en-US and de-DE locales.
Add CI pipeline with static analysis, tests, security audit, and debug build.
Add security gate in release workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:44:40 +01:00
bca7e391ad ci: add CI pipeline with analysis, tests, security audit, and debug build
Add ci.yaml triggered on branch pushes and PRs with flutter analyze,
flutter test, dart pub audit, Trivy scan, and debug APK build. Gate the
release workflow behind a CI job so release builds only proceed after
all checks pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:42:17 +01:00
3902755f61 docs: add project README with features, screenshots, and setup instructions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:03:02 +01:00
8d635970d2 fix(release.yaml): remove accidental text from grep command
All checks were successful
Build and Release to F-Droid / build-and-deploy (push) Successful in 11m33s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:58:27 +01:00
51dba090d6 refactor: switch to MIT License, add tagging instructions, and update F-Droid workflow
- Replaces Apache 2.0 with MIT License and updates copyright details
- Adds tagging instructions to `CLAUDE.md` for semantic versioning and CHANGELOG updates
- Updates release workflow to copy metadata to F-Droid repo
2026-03-17 10:55:46 +01:00
fc5a612b81 feat: add custom house launcher icon, themed splash screen, and F-Droid metadata
Replace default Flutter icon with white house on sage green (#7A9A6D) background.
Add native splash screen with themed colors (#F5F0E8 light, #2A2520 dark).
Include F-Droid metadata with screenshots for de-DE and en-US locales.
Add CHANGELOG.md tracking all releases from v1.0.0 to v1.1.3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:55:32 +01:00
fa778a238a chore(release.yaml): update workflow to set Flutter app version from Git tag
All checks were successful
Build and Release to F-Droid / build-and-deploy (push) Successful in 12m0s
- Adds script to parse Git tag, calculate semantic version and numeric build code
- Updates `pubspec.yaml` with version and build code during release
2026-03-17 09:39:50 +01:00
d220dbe5ce test(TaskListScreen): add integration tests for filtered and overdue task states
All checks were successful
Build and Release to F-Droid / build-and-deploy (push) Successful in 10m30s
- Covers empty states, celebration state, and scheduled/overdue task rendering
- Verifies proper checkbox behavior for future tasks
- Tests AppBar for sort dropdown, edit/delete actions, and calendar strip
- Adds necessary test helpers and overrides for room-specific tasks
2026-03-16 23:35:17 +01:00
edce11dd78 chore: complete v1.1 milestone
Archive v1.1 Calendar & Polish milestone artifacts (roadmap,
requirements, phase directories) to milestones/. Evolve PROJECT.md
with validated requirements and new key decisions. Update
RETROSPECTIVE.md with v1.1 section and cross-milestone trends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 23:32:04 +01:00
0ea79e0853 docs(phase-07): complete phase execution
All checks were successful
Build and Release to F-Droid / build-and-deploy (push) Successful in 10m29s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 22:43:41 +01:00
772034cba1 docs(07-02): complete sort dropdown UI plan
- 07-02-SUMMARY.md: SortDropdown widget wired into HomeScreen and TaskListScreen AppBars
- STATE.md: updated metrics, decisions, session record
- ROADMAP.md: Phase 7 complete (2/2 plans done)
2026-03-16 22:40:39 +01:00
a3e4d0224b feat(07-02): add sort dropdown tests to HomeScreen and fix AppShell test regression
- Add sortPreferenceProvider override to _buildApp test helper
- Add 'HomeScreen sort dropdown' test group: verifies PopupMenuButton<TaskSortOption> and AppBar title
- Fix app_shell_test: expect findsWidgets for 'Übersicht' since AppBar title now also shows it
- 115 tests pass total (113 existing + 2 new)
2026-03-16 22:39:18 +01:00
e5eccb74e5 feat(07-02): build SortDropdown widget and integrate into HomeScreen and TaskListScreen
- Create SortDropdown ConsumerWidget using PopupMenuButton<TaskSortOption>
- Displays current sort label with sort icon in AppBar actions
- Check mark shown on active option via Opacity widget
- Add Scaffold with AppBar (title: Übersicht, actions: SortDropdown) to HomeScreen
- Add SortDropdown before edit/delete IconButtons in TaskListScreen AppBar
2026-03-16 22:37:18 +01:00
9398193c1e docs(07-01): complete task sort domain and provider plan 2026-03-16 22:35:01 +01:00
3697e4efc4 feat(07-01): integrate sort logic into calendarDayProvider and tasksInRoomProvider
- calendarDayProvider watches sortPreferenceProvider and sorts dayTasks
- overdueTasks intentionally unsorted (pinned at top per design decision)
- tasksInRoomProvider watches sortPreferenceProvider and sorts via stream.map
- _sortTasks helper (TaskWithRoom) and _sortTasksRaw helper (Task) both support:
  - alphabetical: case-insensitive A-Z by name
  - interval: by intervalType.index ascending, intervalDays as tiebreaker
  - effort: by effortLevel.index ascending (low→medium→high)
- All 113 tests pass, analyze clean
2026-03-16 22:33:34 +01:00
13c7d623ba feat(07-01): create TaskSortOption enum, SortPreferenceNotifier, and localization strings
- TaskSortOption enum with alphabetical, interval, effort values
- SortPreferenceNotifier persists sort preference to SharedPreferences
- Follows ThemeNotifier pattern: sync default (alphabetical), async load
- Generated sort_preference_notifier.g.dart via build_runner
- Added sortAlphabetical/sortInterval/sortEffort/sortLabel to app_de.arb
- Regenerated app_localizations.dart and app_localizations_de.dart
2026-03-16 22:32:06 +01:00
a9f298350e test(07-01): add failing tests for SortPreferenceNotifier
- Tests cover default state (alphabetical)
- Tests cover setSortOption state update
- Tests cover SharedPreferences persistence
- Tests cover persisted value loaded on restart
- Tests cover unknown persisted value fallback
2026-03-16 22:30:05 +01:00
a44f2b80b5 fix(07): remove invalid -x flag from Task 1 verify command 2026-03-16 22:26:50 +01:00
27f18d4f39 docs(07-task-sorting): create phase plan 2026-03-16 22:23:24 +01:00
a94d41b7f7 docs(state): record phase 7 context session 2026-03-16 22:14:31 +01:00
99358ed704 docs(07): capture phase context 2026-03-16 22:14:13 +01:00
2a4b14cb43 chore(release): improve F-Droid release workflow for repo persistence
- Download entire `fdroid/` directory from Hetzner to retain older APKs, repo keystore, and config.yml.
- Add steps to ensure repo signing key and icons during initialization.
- Adjust SCP upload to include the entire `fdroid/` directory for better state continuity.
2026-03-16 22:13:32 +01:00
7a2c1b81de docs(phase-06): complete phase execution 2026-03-16 22:02:03 +01:00
7344933278 docs(06-01): complete task history plan
- Create 06-01-SUMMARY.md with full execution documentation
- Update STATE.md: Phase 6 Plan 1 complete, decisions recorded
- Update ROADMAP.md: Phase 6 marked complete (1/1 plans)
- Mark HIST-01 and HIST-02 requirements complete
2026-03-16 21:59:00 +01:00
9f902ff2c7 feat(06-01): build task history sheet, wire into TaskFormScreen, add CalendarTaskRow navigation
- Create task_history_sheet.dart: showTaskHistorySheet() modal bottom sheet
- Sheet uses StreamBuilder on watchCompletionsForTask, shows dates in dd.MM.yyyy + HH:mm format
- Empty state: Icons.history + 'Noch nie erledigt' message
- Count summary shown above list when completions exist
- Add Verlauf ListTile to TaskFormScreen (edit mode only) opening history sheet
- Add onTap to CalendarTaskRow navigating to /rooms/:roomId/tasks/:taskId
- All 106 tests pass, zero analyze issues
2026-03-16 21:57:11 +01:00
ceae7d7d61 feat(06-01): add watchCompletionsForTask DAO method and history localization strings
- Add watchCompletionsForTask(taskId) to TasksDao: Stream<List<TaskCompletion>> sorted newest first
- Regenerate tasks_dao.g.dart with build_runner
- Add taskHistoryTitle, taskHistoryEmpty, taskHistoryCount to app_de.arb
- Regenerate app_localizations.dart and app_localizations_de.dart
- All 5 new DAO tests pass, zero analyze issues
2026-03-16 21:55:44 +01:00
2687f5e31e test(06-01): add failing tests for watchCompletionsForTask DAO method
- Tests cover empty state, single completion, multiple completions in reverse order
- Tests cover task isolation (different taskIds do not cross-contaminate)
- Tests cover stream reactivity (new completion triggers emission)
2026-03-16 21:53:21 +01:00
97eaa6dacc chore(release): enhance upload script with SFTP mkdir pre-checks and SCP improvements 2026-03-16 21:51:31 +01:00
03ebaac5a8 docs(06-task-history): create phase plan 2026-03-16 21:50:11 +01:00
dec15204de docs(state): record phase 6 context session 2026-03-16 21:46:23 +01:00
adb46d847e docs(06): capture phase context 2026-03-16 21:46:15 +01:00
b674497003 docs(phase-05): complete phase execution 2026-03-16 21:42:44 +01:00
7536f2f759 chore(release): switch to SCP-only upload, remove rsync dependency 2026-03-16 21:38:37 +01:00
27b1a80f29 docs(05-02): complete calendar strip UI plan
- 05-02-SUMMARY.md: calendar strip UI widgets delivered, 1 auto-fix (test migration)
- STATE.md: Phase 5 complete, 2/2 plans done, decisions recorded
- ROADMAP.md: Phase 5 marked complete (2/2 plans)
- REQUIREMENTS.md: CAL-01, CAL-03, CAL-04 marked complete
2026-03-16 21:38:06 +01:00
88ef248a33 feat(05-02): replace HomeScreen with calendar composition and floating Today button
- Rewrite HomeScreen as ConsumerStatefulWidget composing CalendarStrip + CalendarDayList
- CalendarStripController wires floating Today button to scroll-strip-to-today animation
- FloatingActionButton.extended shows "Heute" + Icons.today only when today is out of viewport
- Old overdue/today/tomorrow stacked plan sections and ProgressCard fully removed
2026-03-16 21:35:54 +01:00
f718ee8483 feat(05-02): build CalendarStrip, CalendarTaskRow, CalendarDayList widgets
- Add totalTaskCount field to CalendarDayState to distinguish first-run from celebration
- Add getTaskCount() to CalendarDao (SELECT COUNT from tasks)
- CalendarStrip: 181-day horizontal scroll with German abbreviations, today highlighting, month boundary labels, scroll-to-today controller
- CalendarTaskRow: task name + room tag chip + checkbox, no relative date, isOverdue coral styling
- CalendarDayList: loading/error/first-run-empty/empty-day/celebration/has-tasks states, overdue section (today only), slide-out completion animation
- Update home_screen_test.dart and app_shell_test.dart to test new calendar providers instead of dailyPlanProvider
2026-03-16 21:35:35 +01:00
01de2d0f9c docs(05-01): complete calendar data layer plan — CalendarDao, providers, l10n
- 05-01-SUMMARY.md: execution results with deviations, decisions, self-check
- STATE.md: current position updated to Plan 01 complete, 3 new decisions
- ROADMAP.md: Phase 5 in progress (1/2 plans complete)
- REQUIREMENTS.md: CAL-02 and CAL-05 marked complete
2026-03-16 21:26:28 +01:00
588f215078 chore(release): improve upload script with retries, directory check, and fallback to scp 2026-03-16 21:25:00 +01:00
68ba7c65ce feat(05-01): add CalendarDayState model, Riverpod providers, and l10n strings
- CalendarDayState: selectedDate, dayTasks, overdueTasks fields with isEmpty helper
- selectedDateProvider: NotifierProvider with SelectedDateNotifier, defaults to today
- calendarDayProvider: StreamProvider.autoDispose, overdue only when viewing today
- Add calendarTodayButton l10n string ("Heute") to ARB and generated dart files
2026-03-16 21:24:07 +01:00
c666f9a1c6 feat(05-01): implement CalendarDao with date-parameterized task queries
- CalendarDao.watchTasksForDate: returns tasks due on a specific calendar day, sorted by name
- CalendarDao.watchOverdueTasks: returns tasks due strictly before reference date, sorted by due date
- Registered CalendarDao in AppDatabase @DriftDatabase annotation
- Generated calendar_dao.g.dart and updated database.g.dart
2026-03-16 21:21:07 +01:00
f5c4b4928f test(05-01): add failing tests for CalendarDao
- watchTasksForDate: empty list, date filter, multi-room, alpha sort, no overdue carry-over
- watchOverdueTasks: empty list, before-date filter, excludes reference date, sorted by date
2026-03-16 21:19:55 +01:00
31d4ef879b docs(05-calendar-strip): create phase plan 2026-03-16 21:14:59 +01:00
fe7ba21061 chore(release): add rsync to release workflow dependencies 2026-03-16 20:58:25 +01:00
90ff66223c docs: create milestone v1.1 roadmap (3 phases) 2026-03-16 20:54:40 +01:00
b7a243603c docs: define milestone v1.1 requirements 2026-03-16 20:50:20 +01:00
fa26d6b301 chore(release): install fdroidserver via pip for compatibility with modern Flutter/AGP APKs 2026-03-16 20:45:09 +01:00
6bb1bc35d7 docs: start milestone v1.1 Calendar & Polish 2026-03-16 20:44:51 +01:00
ead085ad26 fix(android): move MainActivity to correct package to fix ClassNotFoundException
The namespace/applicationId is `de.jeanlucmakiola.household_keeper` but
MainActivity was in the old `com.jlmak.household_keeper` package, causing
a crash on launch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 20:39:08 +01:00
74a801c6f2 chore(release): enhance release workflow to safely handle APK naming with ref-based fallback 2026-03-16 20:30:40 +01:00
0059095e38 chore(release): make sudo usage optional in release workflow setup steps 2026-03-16 20:14:31 +01:00
8c72403c85 chore: archive v1.0 phase directories to milestones/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 20:12:01 +01:00
1a1a10c9ea chore: complete v1.0 MVP milestone
Some checks failed
Build and Release to F-Droid / build-and-deploy (push) Has been cancelled
Archive roadmap and requirements to milestones/, evolve PROJECT.md
with validated requirements and decision outcomes, reorganize
ROADMAP.md with milestone grouping, create retrospective.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 20:10:01 +01:00
36126acc18 chore(android): migrate build.gradle.kts to use Kotlin idioms for properties and improve readability 2026-03-16 19:51:53 +01:00
76192e22fa chore(release): improve Android SDK setup and toolchain verification in workflow 2026-03-16 19:42:04 +01:00
9c2ae12012 chore(release): add Android SDK setup steps to workflow 2026-03-16 19:36:09 +01:00
dcb2cd0afa chore(release): trust Flutter SDK directories in workflow to fix safe directory errors 2026-03-16 19:29:50 +01:00
c2570cdc01 chore(release): remove explicit Flutter version specification in workflow 2026-03-16 19:09:37 +01:00
3c2ad5c7c6 chore(release): add jq installation step in workflow for JSON parsing 2026-03-16 19:06:59 +01:00
f6272a39b4 chore(release): update workflow to use Docker runner instead of Ubuntu 2026-03-16 19:00:14 +01:00
199 changed files with 10651 additions and 2208 deletions

120
.gitea/workflows/ci.yaml Normal file
View File

@@ -0,0 +1,120 @@
name: CI
on:
push:
branches:
- '**'
tags-ignore:
- '**'
pull_request:
jobs:
ci:
runs-on: docker
env:
ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Install Android SDK packages
run: |
sdkmanager --licenses >/dev/null <<'EOF'
y
y
y
y
y
y
y
y
y
y
EOF
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
- name: Install jq
run: |
set -e
SUDO=""
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
fi
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y jq
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache jq
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y jq
elif command -v yum >/dev/null 2>&1; then
$SUDO yum install -y jq
else
echo "Could not find a supported package manager to install jq"
exit 1
fi
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Trust Flutter SDK git directory
run: |
set -e
FLUTTER_BIN_DIR="$(dirname "$(command -v flutter)")"
FLUTTER_SDK_DIR="$(cd "$FLUTTER_BIN_DIR/.." && pwd -P)"
git config --global --add safe.directory "$FLUTTER_SDK_DIR"
if [ -n "${FLUTTER_ROOT:-}" ]; then
git config --global --add safe.directory "$FLUTTER_ROOT"
fi
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.4-x64 || true
- name: Verify Android + Flutter toolchain
run: flutter doctor -v
- name: Install dependencies
run: flutter pub get
- name: Static analysis
run: flutter analyze --no-pub
- name: Run tests
run: flutter test
- name: Check outdated dependencies
run: dart pub outdated
continue-on-error: true
- name: Trivy filesystem scan
run: |
set -e
SUDO=""
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
fi
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | $SUDO tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | $SUDO tee /etc/apt/sources.list.d/trivy.list
$SUDO apt-get update
$SUDO apt-get install -y trivy
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache trivy || (wget -qO trivy.tar.gz https://github.com/aquasecurity/trivy/releases/latest/download/trivy_0.62.1_Linux-64bit.tar.gz && tar xzf trivy.tar.gz trivy && $SUDO mv trivy /usr/local/bin/)
fi
trivy filesystem --severity HIGH,CRITICAL --exit-code 0 .
continue-on-error: true
- name: Build debug APK
run: flutter build apk --debug

View File

@@ -7,8 +7,11 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-and-deploy: ci:
runs-on: ubuntu-latest runs-on: docker
env:
ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -19,15 +22,205 @@ jobs:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Install Android SDK packages
run: |
sdkmanager --licenses >/dev/null <<'EOF'
y
y
y
y
y
y
y
y
y
y
EOF
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
- name: Install jq
run: |
set -e
SUDO=""
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
fi
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y jq
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache jq
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y jq
elif command -v yum >/dev/null 2>&1; then
$SUDO yum install -y jq
else
echo "Could not find a supported package manager to install jq"
exit 1
fi
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.11.0'
channel: 'stable' channel: 'stable'
- name: Trust Flutter SDK git directory
run: |
set -e
FLUTTER_BIN_DIR="$(dirname "$(command -v flutter)")"
FLUTTER_SDK_DIR="$(cd "$FLUTTER_BIN_DIR/.." && pwd -P)"
git config --global --add safe.directory "$FLUTTER_SDK_DIR"
if [ -n "${FLUTTER_ROOT:-}" ]; then
git config --global --add safe.directory "$FLUTTER_ROOT"
fi
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.4-x64 || true
- name: Verify Android + Flutter toolchain
run: flutter doctor -v
- name: Install dependencies - name: Install dependencies
run: flutter pub get run: flutter pub get
- name: Static analysis
run: flutter analyze --no-pub
- name: Run tests
run: flutter test
- name: Check outdated dependencies
run: dart pub outdated
continue-on-error: true
- name: Trivy filesystem scan
run: |
set -e
SUDO=""
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
fi
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | $SUDO tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | $SUDO tee /etc/apt/sources.list.d/trivy.list
$SUDO apt-get update
$SUDO apt-get install -y trivy
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache trivy || (wget -qO trivy.tar.gz https://github.com/aquasecurity/trivy/releases/latest/download/trivy_0.62.1_Linux-64bit.tar.gz && tar xzf trivy.tar.gz trivy && $SUDO mv trivy /usr/local/bin/)
fi
trivy filesystem --severity HIGH,CRITICAL --exit-code 0 .
continue-on-error: true
- name: Build debug APK
run: flutter build apk --debug
build-and-deploy:
needs: ci
runs-on: docker
env:
ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Install Android SDK packages
run: |
sdkmanager --licenses >/dev/null <<'EOF'
y
y
y
y
y
y
y
y
y
y
EOF
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
- name: Install jq
run: |
set -e
SUDO=""
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
fi
if command -v apt-get >/dev/null 2>&1; then
$SUDO apt-get update
$SUDO apt-get install -y jq
elif command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache jq
elif command -v dnf >/dev/null 2>&1; then
$SUDO dnf install -y jq
elif command -v yum >/dev/null 2>&1; then
$SUDO yum install -y jq
else
echo "Could not find a supported package manager to install jq"
exit 1
fi
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Trust Flutter SDK git directory
run: |
set -e
FLUTTER_BIN_DIR="$(dirname "$(command -v flutter)")"
FLUTTER_SDK_DIR="$(cd "$FLUTTER_BIN_DIR/.." && pwd -P)"
git config --global --add safe.directory "$FLUTTER_SDK_DIR"
if [ -n "${FLUTTER_ROOT:-}" ]; then
git config --global --add safe.directory "$FLUTTER_ROOT"
fi
# Runner-specific fallback observed in failing logs
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.4-x64 || true
- name: Verify Android + Flutter toolchain
run: flutter doctor -v
- name: Install dependencies
run: flutter pub get
- name: Set version from git tag
run: |
set -e
RAW_TAG="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}"
# Strip leading 'v' if present (v1.2.3 -> 1.2.3)
VERSION="${RAW_TAG#v}"
# Extract a numeric build number for versionCode.
# Converts semver x.y.z into a single integer: x*10000 + y*100 + z
# e.g. 1.1.1 -> 10101, 2.3.15 -> 20315
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)
MAJOR=${MAJOR:-0}; MINOR=${MINOR:-0}; PATCH=${PATCH:-0}
VERSION_CODE=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
echo "Version: $VERSION, VersionCode: $VERSION_CODE"
# Update pubspec.yaml: replace the version line
sed -i "s/^version: .*/version: ${VERSION}+${VERSION_CODE}/" pubspec.yaml
grep '^version:' pubspec.yaml
# ADD THIS NEW STEP # ADD THIS NEW STEP
- name: Setup Android Keystore - name: Setup Android Keystore
env: env:
@@ -49,8 +242,15 @@ jobs:
- name: Setup F-Droid Server Tools - name: Setup F-Droid Server Tools
run: | run: |
sudo apt-get update SUDO=""
sudo apt-get install -y fdroidserver sshpass if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
fi
$SUDO apt-get update
# sshpass from apt, fdroidserver via pip to get a newer androguard that
# can parse modern Flutter/AGP APKs (apt ships fdroidserver 2.2.1 which crashes)
$SUDO apt-get install -y sshpass python3-pip
pip3 install --break-system-packages --upgrade fdroidserver
- name: Initialize or fetch F-Droid Repository - name: Initialize or fetch F-Droid Repository
env: env:
@@ -59,18 +259,53 @@ jobs:
PASS: ${{ secrets.HETZNER_PASS }} PASS: ${{ secrets.HETZNER_PASS }}
run: | run: |
mkdir -p fdroid mkdir -p fdroid
# Ensure remote path exists (sftp mkdir, ignoring errors if already present).
sshpass -p "$PASS" sftp -o StrictHostKeyChecking=no "$USER@$HOST" <<'SFTP'
-mkdir dev
-mkdir dev/fdroid
-mkdir dev/fdroid/repo
SFTP
# Try to download the entire fdroid/ directory from Hetzner to keep
# older APKs, the repo keystore, and config.yml across runs.
# If it fails (first time), initialize a new local repo.
sshpass -p "$PASS" scp -o StrictHostKeyChecking=no -r "$USER@$HOST:dev/fdroid/." fdroid/ || (cd fdroid && fdroid init)
- name: Ensure F-Droid repo signing key and icon
run: |
cd fdroid cd fdroid
# Try to download the existing repo/ folder from Hetzner to keep older versions and the keystore # Ensure repo icon exists (use app launcher icon)
# If it fails (first time), we just initialize a new one mkdir -p repo/icons
sshpass -p "$PASS" scp -o StrictHostKeyChecking=no -r $USER@$HOST:dev/fdroid/repo . || fdroid init if [ ! -f repo/icons/icon.png ]; then
cp ../android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png repo/icons/icon.png
fi
# If keystore doesn't exist, create the signing key.
# This only runs on the very first deployment; subsequent runs
# download the keystore from Hetzner via the scp step above.
if [ ! -f keystore.p12 ]; then
fdroid update --create-key
fi
- name: Copy new APK to repo - name: Copy new APK to repo
run: | run: |
# The app-release.apk name should ideally include the version number set -e
# so it doesn't overwrite older versions in the repo. mkdir -p fdroid/repo
VERSION_TAG=${GITHUB_REF#refs/tags/} # gets 'v1.0.0'
cp build/app/outputs/flutter-apk/app-release.apk fdroid/repo/my_flutter_app_${VERSION_TAG}.apk # Prefer tag name for release builds; fallback to ref name for manual runs.
REF_NAME="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}"
SAFE_REF_NAME="$(echo "$REF_NAME" | tr '/ ' '__' | tr -cd '[:alnum:]_.-')"
if [ -z "$SAFE_REF_NAME" ]; then
SAFE_REF_NAME="${GITHUB_SHA:-manual}"
fi
cp build/app/outputs/flutter-apk/app-release.apk "fdroid/repo/my_flutter_app_${SAFE_REF_NAME}.apk"
- name: Copy metadata to F-Droid repo
run: |
cp -r fdroid-metadata/* fdroid/metadata/
- name: Generate F-Droid Index - name: Generate F-Droid Index
run: | run: |
@@ -83,5 +318,17 @@ jobs:
USER: ${{ secrets.HETZNER_USER }} USER: ${{ secrets.HETZNER_USER }}
PASS: ${{ secrets.HETZNER_PASS }} PASS: ${{ secrets.HETZNER_PASS }}
run: | run: |
# Use rsync to efficiently upload only the changed files (the new APK and updated index files) set -euo pipefail
sshpass -p "$PASS" rsync -avz -e "ssh -o StrictHostKeyChecking=no" fdroid/repo/ $USER@$HOST:dev/fdroid/repo/ SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=20"
# Create remote directory tree via SFTP batch (no exec channel needed).
# Leading '-' on each mkdir means "ignore error if already exists".
sshpass -p "$PASS" sftp $SSH_OPTS "$USER@$HOST" <<'SFTP'
-mkdir dev
-mkdir dev/fdroid
-mkdir dev/fdroid/repo
SFTP
# Upload the entire fdroid/ directory (repo + keystore + config)
# so the signing key persists across runs.
sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/. "$USER@$HOST:dev/fdroid/"

37
.planning/MILESTONES.md Normal file
View File

@@ -0,0 +1,37 @@
# Milestones
## v1.1 Calendar & Polish (Shipped: 2026-03-16)
**Phases completed:** 3 phases, 5 plans, 11 tasks
**Codebase:** 13,031 LOC Dart (9,051 lib + 3,980 test), 108 tests, 41 commits
**Timeline:** 2 days (2026-03-15 to 2026-03-16)
**Key accomplishments:**
1. Horizontal 181-day calendar strip with German day cards, month boundaries, and floating Today button — replaces the stacked daily-plan HomeScreen
2. Date-parameterized CalendarDao with reactive Drift streams for day tasks and overdue tasks
3. Task completion history bottom sheet with per-task reverse-chronological log
4. Alphabetical, interval, and effort sort options persisted via SharedPreferences
5. SortDropdown widget integrated in both HomeScreen and TaskListScreen AppBars
**Archive:** See `milestones/v1.1-ROADMAP.md` and `milestones/v1.1-REQUIREMENTS.md`
---
## v1.0 MVP (Shipped: 2026-03-16)
**Phases completed:** 4 phases, 13 plans
**Codebase:** 10,588 LOC Dart (7,773 lib + 2,815 test), 89 tests, 76 commits
**Timeline:** 2 days (2026-03-15 to 2026-03-16)
**Key accomplishments:**
1. Flutter project with Drift SQLite, Riverpod 3 state management, ARB localization, and calm sage & stone Material 3 theme
2. Full room CRUD with drag-and-drop reorder, icon picker, and cleanliness indicator per room card
3. Task CRUD with 11 frequency presets + custom intervals, calendar-anchored scheduling with anchor memory, and auto-calculated next due dates
4. Bundled German-language task templates for 14 room types with post-creation template picker
5. Daily plan home screen with overdue/today/tomorrow sections, animated checkbox completion, and progress tracking
6. Daily summary notification with configurable time, POST_NOTIFICATIONS permission handling, and boot receiver rescheduling
**Archive:** See `milestones/v1.0-ROADMAP.md` and `milestones/v1.0-REQUIREMENTS.md`
---

View File

@@ -2,30 +2,46 @@
## What This Is ## What This Is
A local-first Flutter app for organizing household chores and one-time projects, built for personal/couple use on Android. Takes the room-based task scheduling model (inspired by BeTidy), strips cloud/account/social features, and wraps it in a calm, minimal Material 3 UI. Fully offline, free, privacy-respecting — all data stays on-device. A local-first Flutter app for organizing household chores, built for personal/couple use on Android. Uses a room-based task scheduling model where users create rooms, add recurring tasks with frequency intervals, and the app auto-calculates the next due date after each completion. Features a horizontal calendar strip home screen with day-by-day task navigation, task completion history, configurable sorting (alphabetical, interval, effort), bundled German-language task templates, room cleanliness indicators, and daily summary notifications. Fully offline, free, privacy-respecting — all data stays on-device.
## Core Value ## Core Value
Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it. Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it.
## Current Milestone: v1.2 Polish & Task Management
**Goal:** Add task delete with smart soft/hard behavior, rework the task creation frequency picker for better UX, and clean up dead code from v1.0.
**Target features:**
- Delete action in task edit form (hard delete if never completed, soft delete if completed at least once)
- Intuitive "Every [N] [unit]" frequency picker replacing the flat preset chip grid
- Common frequency shortcuts (daily, weekly, biweekly, monthly) as quick-select
- Dead code cleanup (orphaned v1.0 daily plan files)
## Requirements ## Requirements
### Validated ### Validated
(None yet — ship to validate) - Room CRUD with icons and drag-and-drop reorder — v1.0
- Task CRUD with frequency intervals and due date calculation — v1.0
- Daily plan view with overdue/today/upcoming sections — v1.0
- Task completion with auto-scheduling of next due date — v1.0
- Bundled task templates per room type (German only, 14 room types) — v1.0
- Daily summary notification with configurable time — v1.0
- Light/dark theme with calm Material 3 palette — v1.0
- Cleanliness indicator per room (based on overdue vs on-time) — v1.0
- Horizontal calendar strip home screen replacing stacked daily plan — v1.1
- Overdue task carry-over with red/orange visual accent — v1.1
- Task completion history with per-task reverse-chronological log — v1.1
- Alphabetical, interval, and effort task sorting with persistence — v1.1
### Active ### Active
- [ ] Room CRUD with icons and optional photos - [ ] Task delete with smart soft/hard behavior
- [ ] Task CRUD with frequency intervals and due date calculation - [ ] Task creation frequency picker UX rework
- [ ] Daily plan view with overdue/today/upcoming sections - [ ] Dead code cleanup (v1.0 daily plan files)
- [ ] Task completion with auto-scheduling of next due date - [ ] Data export/import (JSON) — deferred
- [ ] Bundled task templates per room type (German only) - [ ] English localization — deferred
- [ ] Daily summary notification
- [ ] Light/dark theme with calm Material 3 palette
- [ ] Cleanliness indicator per room (based on overdue vs on-time)
- [ ] Task sorting (due date, alphabetical, interval, effort)
- [ ] Task history (completion log per task)
### Out of Scope ### Out of Scope
@@ -34,22 +50,28 @@ Users can see what needs doing today, mark it done, and trust the app to schedul
- Subscription model / in-app purchases — free forever - Subscription model / in-app purchases — free forever
- Family profile sharing across devices — single-device app - Family profile sharing across devices — single-device app
- Server-side infrastructure — zero backend - Server-side infrastructure — zero backend
- Data export/import (JSON) — deferred to v1.1 - AI-powered task suggestions — overkill for curated templates
- English localization — deferred to v1.1 (ship German-only MVP) - Per-task push notifications — daily summary is more effective
- Firebase or any Google cloud services — contradicts local-first design
- Real-time cross-device sync — potential future self-hosted feature - Real-time cross-device sync — potential future self-hosted feature
- Tablet-optimized layout — future enhancement - Tablet-optimized layout — future enhancement
- Weekly/monthly calendar views — date strip is sufficient for task app
- Drag tasks between days — tasks auto-schedule based on frequency
- Calendar sync (Google/Apple) — contradicts local-first, offline-only design
- Room cover photos from camera or gallery — dropped, clean design preferred
- Statistics & insights dashboard — v2.0 - Statistics & insights dashboard — v2.0
- Onboarding wizard — v2.0 - Onboarding wizard — v2.0
- Custom accent color picker — v2.0 - Custom accent color picker — v2.0
## Context ## Context
- Inspired by BeTidy (iOS/Android household cleaning app) — taking the proven room-based model, removing cloud/social, refining the UI - Shipped v1.1 with 13,031 LOC Dart (9,051 lib + 3,980 test), 108 tests
- Tech stack: Flutter + Dart, Riverpod 3 + code generation, Drift 2.31 SQLite, GoRouter, flutter_local_notifications, SharedPreferences
- Inspired by BeTidy (iOS/Android household cleaning app) — room-based model, no cloud/social
- Built for personal use with partner on a shared Android device; may publish publicly later - Built for personal use with partner on a shared Android device; may publish publicly later
- Code and comments in English; UI strings German-only for MVP - Code and comments in English; UI strings German-only through v1.1
- Room photos are nice-to-have for MVP — icon-only rooms are sufficient initially
- Developer is new to Drift (SQLite ORM) — plan should account for learning curve
- Gitea (self-hosted on Hetzner) for version control; no CI/CD pipeline yet - Gitea (self-hosted on Hetzner) for version control; no CI/CD pipeline yet
- Dead code from v1.0: daily_plan_providers.dart, daily_plan_task_row.dart, progress_card.dart (DailyPlanDao still used by notification service)
## Constraints ## Constraints
@@ -57,20 +79,28 @@ Users can see what needs doing today, mark it done, and trust the app to schedul
- **Platform**: Android-first (iOS later) - **Platform**: Android-first (iOS later)
- **Offline**: 100% offline-capable, zero network dependencies - **Offline**: 100% offline-capable, zero network dependencies
- **Privacy**: No data leaves the device, no analytics, no tracking - **Privacy**: No data leaves the device, no analytics, no tracking
- **Language**: German-only UI for MVP, English code/comments - **Language**: German-only UI through v1.1, English code/comments
- **No CI**: No automated build pipeline initially - **No CI**: No automated build pipeline initially
## Key Decisions ## Key Decisions
| Decision | Rationale | Outcome | | Decision | Rationale | Outcome |
|----------|-----------|---------| |----------|-----------|---------|
| Riverpod over Bloc | Modern, compile-safe, less boilerplate, Dart-native | — Pending | | Riverpod 3 over Bloc | Modern, compile-safe, less boilerplate, Dart-native | Good — code generation works well, @riverpod annotation reduces boilerplate |
| Drift over raw sqflite | Type-safe queries, compile-time validation, migration support | — Pending | | Drift over raw sqflite | Type-safe queries, compile-time validation, migration support | Good — DAOs with stream queries provide reactive UI, migration workflow established |
| Android-first | Primary device is Android; iOS follows | — Pending | | Android-first | Primary device is Android; iOS follows | Good — no iOS-specific issues encountered |
| German-only MVP | Primary user language; defer localization infrastructure | — Pending | | German-only MVP | Primary user language; defer localization infrastructure | Good — ARB localization infrastructure in place from Phase 1, ready for English |
| No CI initially | Keep scope focused on the app itself | — Pending | | No CI initially | Keep scope focused on the app itself | Good — manual dart analyze + flutter test sufficient for solo dev |
| Calm Material 3 palette | Muted greens, warm grays, gentle blues — calm productivity, not playful | — Pending | | Calm Material 3 palette | Muted greens, warm grays, gentle blues — calm productivity | Good — sage & stone theme (seed 0xFF7A9A6D) with warm charcoal dark mode |
| Clean Architecture | Feature-based folder structure with data/domain/presentation layers | — Pending | | Clean Architecture | Feature-based folder structure with data/domain/presentation layers | Good — clear separation, easy to navigate |
| Calendar-anchored scheduling | Monthly/quarterly/yearly tasks anchor to original day-of-month with clamping | Good — handles Feb 28/31 edge cases correctly with anchor memory |
| flutter_local_notifications v21 | Standard Flutter notification package, TZ-aware scheduling | Good — inexactAllowWhileIdle avoids SCHEDULE_EXACT_ALARM complexity |
| Manual StreamProvider for drift types | riverpod_generator throws InvalidTypeException with drift Task type | Revisit — may be fixed in future riverpod_generator versions |
| Calendar strip replaces daily plan | v1.1 goal — stacked overdue/today/upcoming sections replaced by horizontal 181-day strip | Good — cleaner navigation, day-by-day browsing |
| NotifierProvider over StateProvider | Riverpod 3.x removed StateProvider | Good — minimal Notifier subclass works cleanly |
| In-memory sort over SQL ORDER BY | Sort preference changes without re-querying DB | Good — stream.map applies sort after DB emit, reactive to preference changes |
| SharedPreferences for sort | Simple enum.name string persistence for sort preference | Good — lightweight, no DB migration needed, survives app restart |
| PopupMenuButton for sort UI | Material 3 AppBar action pattern, overlay menu | Good — clean integration in both HomeScreen and TaskListScreen AppBars |
--- ---
*Last updated: 2026-03-15 after initialization* *Last updated: 2026-03-18 after v1.2 milestone started*

View File

@@ -1,102 +1,48 @@
# Requirements: HouseHoldKeaper # Requirements: HouseHoldKeaper
**Defined:** 2026-03-15 **Defined:** 2026-03-18
**Core Value:** Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it. **Core Value:** Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it.
## v1 Requirements ## v1.2 Requirements
Requirements for initial release. Each maps to roadmap phases. Requirements for milestone v1.2 Polish & Task Management. Each maps to roadmap phases.
### Room Management ### Task Delete
- [x] **ROOM-01**: User can create a room with a name and an icon from a curated Material Icons set - [x] **DEL-01**: User can delete a task from the task edit form via a clearly visible delete action
- [x] **ROOM-02**: User can edit a room's name and icon - [x] **DEL-02**: Deleting a task that has never been completed removes it from the database entirely (hard delete)
- [x] **ROOM-03**: User can delete a room with confirmation (cascades to associated tasks) - [x] **DEL-03**: Deleting a task that has been completed at least once deactivates it instead (soft delete) — task is hidden from all active views but preserved in the database for future statistics
- [x] **ROOM-04**: User can reorder rooms via drag-and-drop on the rooms screen - [x] **DEL-04**: User sees a confirmation before deleting/deactivating a task
- [x] **ROOM-05**: User can view all rooms as cards showing name, icon, due task count, and cleanliness indicator
### Task Management ### Task Creation UX
- [x] **TASK-01**: User can create a task within a room with name, optional description, frequency interval, and effort level - [x] **TCX-01**: Frequency picker presents an intuitive "Every [N] [unit]" interface instead of a flat grid of preset chips
- [x] **TASK-02**: User can edit a task's name, description, frequency interval, and effort level - [x] **TCX-02**: Common frequencies (daily, weekly, biweekly, monthly) are available as quick-select shortcuts without scrolling through all options
- [x] **TASK-03**: User can delete a task with confirmation - [x] **TCX-03**: User can set any arbitrary interval (e.g., every 5 days, every 3 weeks, every 2 months) without needing to select "Custom" first
- [x] **TASK-04**: User can set frequency interval from: daily, every 2 days, every 3 days, weekly, biweekly, monthly, every 2 months, quarterly, every 6 months, yearly, or custom (every N days) - [x] **TCX-04**: The frequency picker preserves all existing interval types and scheduling behavior (calendar-anchored monthly/quarterly/yearly with anchor memory)
- [x] **TASK-05**: User can set effort level (low / medium / high) on a task
- [x] **TASK-06**: User can sort tasks within a room by due date (default sort order)
- [x] **TASK-07**: User can mark a task as done via tap or swipe, which records a completion timestamp and auto-calculates the next due date based on the interval
- [x] **TASK-08**: Overdue tasks are visually highlighted with distinct color/badge on room cards and in task lists
### Task Templates ### Cleanup
- [x] **TMPL-01**: When creating a room, user can select from bundled German-language task templates appropriate for that room type - [x] **CLN-01**: Dead code from v1.0 daily plan (daily_plan_providers.dart, daily_plan_task_row.dart, progress_card.dart) is removed without breaking notification service (DailyPlanDao must be preserved)
- [x] **TMPL-02**: Preset room types with templates include: Küche, Badezimmer, Schlafzimmer, Wohnzimmer, Flur, Büro, Garage, Balkon, Waschküche, Keller, Kinderzimmer, Gästezimmer, Esszimmer, Garten/Außenbereich
### Daily Plan ## Future Requirements
- [x] **PLAN-01**: User sees all tasks due today grouped by room on the daily plan screen (primary/default screen)
- [x] **PLAN-02**: Overdue tasks appear in a separate highlighted section at the top of the daily plan
- [x] **PLAN-03**: User can preview upcoming tasks (tomorrow / this week)
- [x] **PLAN-04**: User can swipe-to-complete or tap checkbox to mark tasks done directly from the daily plan view
- [x] **PLAN-05**: User sees a progress indicator showing completed vs total tasks for today (e.g. "5 of 12 tasks done")
- [x] **PLAN-06**: When no tasks are due, user sees an encouraging "all clear" empty state
### Cleanliness Indicator
- [x] **CLEAN-01**: Each room card displays a cleanliness indicator derived from the ratio of overdue tasks to total tasks in that room
### Notifications
- [x] **NOTF-01**: User receives a daily summary notification showing today's task count at a configurable time
- [x] **NOTF-02**: User can enable/disable notifications in settings
### Theme & UI
- [x] **THEME-01**: App supports light and dark themes, following the system setting by default
- [x] **THEME-02**: App uses a calm Material 3 palette with muted greens, warm grays, and gentle blues
### Foundation
- [x] **FOUND-01**: App uses Drift for local SQLite storage with proper schema migration workflow
- [x] **FOUND-02**: App uses Riverpod 3 for state management with code generation
- [x] **FOUND-03**: App uses localization infrastructure (ARB files + AppLocalizations) with German locale, even though only one language ships in v1
- [x] **FOUND-04**: Bottom navigation with tabs: Home (Daily Plan), Rooms, Settings
## v2 Requirements
Deferred to future release. Tracked but not in current roadmap. Deferred to future release. Tracked but not in current roadmap.
### v1.1 — Near-Term ### Data
- **EXPORT-01**: User can export all data as JSON file - **DATA-01**: User can export all data as JSON
- **EXPORT-02**: User can import data from a JSON file - **DATA-02**: User can import data from JSON backup
- **I18N-01**: App supports English as a second language
- **PHOTO-01**: User can add a cover photo to a room from camera or gallery
- **HIST-01**: User can view a completion history log per task (scrollable timeline of completion dates)
- **SORT-01**: User can sort tasks by alphabetical order, interval length, or effort level (in addition to due date)
### v1.2 — Medium-Term ### Localization
- **PROJ-01**: User can create one-time organization projects with sub-task steps - **LOC-01**: User can switch UI language to English
- **PROJ-02**: User can attach before/after photos to a project
- **PROF-01**: User can create named local profiles for household members
- **PROF-02**: User can assign tasks to one or more profiles
- **PROF-03**: User can enable task rotation (round-robin) for shared recurring tasks
- **PROF-04**: User can filter the daily plan view by profile ("My tasks" vs "All tasks")
- **WIDG-01**: Home screen widget showing today's due tasks and overdue count
- **CAL-01**: User can view a weekly overview with task load per day
- **CAL-02**: User can view a monthly calendar heatmap showing task density
- **VAC-01**: User can pause/freeze all task due dates during vacation and resume on return
### v2.0 — Future ### v2.0
- **STAT-01**: User can view completion rate (% on time this week/month) - **ONB-01**: Onboarding wizard for first-time users
- **STAT-02**: User can view streak of consecutive days with all tasks completed - **ACC-01**: User can pick a custom accent color for the app theme
- **STAT-03**: User can view per-room health scores over time - **STAT-01**: Statistics & insights dashboard showing task completion trends
- **ONBRD-01**: First-launch wizard walks user through creating first room and adding tasks
- **COLOR-01**: User can pick a custom accent color for the app theme
- **SYNC-01**: User can optionally sync data via self-hosted infrastructure
- **TABLET-01**: App provides a tablet-optimized layout with adaptive breakpoints
- **NOTF-03**: Optional evening nudge notification if overdue tasks remain
## Out of Scope ## Out of Scope
@@ -104,14 +50,20 @@ Explicitly excluded. Documented to prevent scope creep.
| Feature | Reason | | Feature | Reason |
|---------|--------| |---------|--------|
| User accounts & cloud sync | Local-only by design — zero backend, zero data leaves device | | Room cover photos from camera or gallery | Dropped — clean design system preferred |
| Leaderboards & points ranking | Anti-feature — gamification causes app burnout and embeds unequal labor dynamics | | User accounts & cloud sync | Local-only by design |
| Subscription model / in-app purchases | Free forever by design — paywalls are the #2 complaint in chore app reviews | | Leaderboards & points ranking | Not a gamification app |
| Family profile sharing across devices | Single-device app; cross-device requires cloud infrastructure | | Subscription model / in-app purchases | Free forever |
| AI-powered task suggestions | Requires network/ML; overkill for personal app with curated templates | | Family profile sharing across devices | Single-device app |
| Per-task push notifications | Causes notification fatigue; daily summary is more effective for habit formation | | Server-side infrastructure | Zero backend |
| Focus timer / Pomodoro | Not a productivity timer app; out of domain | | AI-powered task suggestions | Overkill for curated templates |
| Firebase or any Google cloud services | Contradicts local-first, privacy-first design | | Per-task push notifications | Daily summary is more effective |
| Firebase or any Google cloud services | Contradicts local-first design |
| Real-time cross-device sync | Potential future self-hosted feature |
| Tablet-optimized layout | Future enhancement |
| Weekly/monthly calendar views | Date strip is sufficient for task app |
| Drag tasks between days | Tasks auto-schedule based on frequency |
| Calendar sync (Google/Apple) | Contradicts local-first, offline-only design |
## Traceability ## Traceability
@@ -119,42 +71,21 @@ Which phases cover which requirements. Updated during roadmap creation.
| Requirement | Phase | Status | | Requirement | Phase | Status |
|-------------|-------|--------| |-------------|-------|--------|
| FOUND-01 | Phase 1: Foundation | Complete | | DEL-01 | Phase 8 | Planned |
| FOUND-02 | Phase 1: Foundation | Complete | | DEL-02 | Phase 8 | Planned |
| FOUND-03 | Phase 1: Foundation | Complete | | DEL-03 | Phase 8 | Planned |
| FOUND-04 | Phase 1: Foundation | Complete | | DEL-04 | Phase 8 | Planned |
| THEME-01 | Phase 1: Foundation | Complete | | TCX-01 | Phase 9 | Planned |
| THEME-02 | Phase 1: Foundation | Complete | | TCX-02 | Phase 9 | Planned |
| ROOM-01 | Phase 2: Rooms and Tasks | Complete | | TCX-03 | Phase 9 | Planned |
| ROOM-02 | Phase 2: Rooms and Tasks | Complete | | TCX-04 | Phase 9 | Planned |
| ROOM-03 | Phase 2: Rooms and Tasks | Complete | | CLN-01 | Phase 10 | Planned |
| ROOM-04 | Phase 2: Rooms and Tasks | Complete |
| ROOM-05 | Phase 2: Rooms and Tasks | Complete |
| TASK-01 | Phase 2: Rooms and Tasks | Complete |
| TASK-02 | Phase 2: Rooms and Tasks | Complete |
| TASK-03 | Phase 2: Rooms and Tasks | Complete |
| TASK-04 | Phase 2: Rooms and Tasks | Complete |
| TASK-05 | Phase 2: Rooms and Tasks | Complete |
| TASK-06 | Phase 2: Rooms and Tasks | Complete |
| TASK-07 | Phase 2: Rooms and Tasks | Complete |
| TASK-08 | Phase 2: Rooms and Tasks | Complete |
| TMPL-01 | Phase 2: Rooms and Tasks | Complete |
| TMPL-02 | Phase 2: Rooms and Tasks | Complete |
| PLAN-01 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-02 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-03 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-04 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-05 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-06 | Phase 3: Daily Plan and Cleanliness | Complete |
| CLEAN-01 | Phase 3: Daily Plan and Cleanliness | Complete |
| NOTF-01 | Phase 4: Notifications | Complete |
| NOTF-02 | Phase 4: Notifications | Complete |
**Coverage:** **Coverage:**
- v1 requirements: 30 total - v1.2 requirements: 9 total
- Mapped to phases: 30 - Mapped to phases: 9
- Unmapped: 0 - Unmapped: 0
--- ---
*Requirements defined: 2026-03-15* *Requirements defined: 2026-03-18*
*Last updated: 2026-03-15 after roadmap creation* *Last updated: 2026-03-18 after roadmap creation (phases 8-10)*

113
.planning/RETROSPECTIVE.md Normal file
View File

@@ -0,0 +1,113 @@
# Project Retrospective
*A living document updated after each milestone. Lessons feed forward into future planning.*
## Milestone: v1.0 — MVP
**Shipped:** 2026-03-16
**Phases:** 4 | **Plans:** 13
### What Was Built
- Complete room-based household chore app with auto-scheduling task management
- Daily plan home screen with overdue/today/tomorrow sections and progress tracking
- Bundled German task templates for 14 room types
- Daily summary notifications with configurable time and Android permission handling
- 89 tests covering DAOs, scheduling logic, providers, and widget behavior
### What Worked
- Bottom-up phase structure (foundation -> data -> UI -> polish) kept each phase clean with minimal rework
- TDD approach for providers and services caught several issues early (async race conditions, API mismatches)
- Verification gates at the end of Phase 2, 3, and 4 confirmed all requirements before moving on
- Calendar-anchored scheduling with anchor memory was designed right the first time — no rework needed
- ARB localization from Phase 1 meant adding German strings was frictionless throughout
### What Was Inefficient
- riverpod_generator InvalidTypeException with drift Task type required workaround (manual StreamProvider) in 3 separate plans — should have been caught in Phase 1 research
- Some plan specifications referenced outdated API patterns (flutter_local_notifications positional parameters removed in v20+) — research needs to verify exact current API signatures
- Phase 4 plan checkboxes in ROADMAP.md weren't updated to [x] by executor — minor bookkeeping gap
### Patterns Established
- `@Riverpod(keepAlive: true)` AsyncNotifier with SharedPreferences for persistent settings (ThemeNotifier, NotificationSettingsNotifier)
- Manual StreamProvider.family/autoDispose for drift type compatibility
- DailyPlanDao innerJoin pattern for cross-table queries
- ConsumerStatefulWidget for screens with async callbacks requiring `mounted` guards
- Provider override pattern in widget tests for database isolation
### Key Lessons
1. Research phase should verify exact current package API signatures — breaking changes between major versions cause plan deviations
2. Drift + riverpod_generator type incompatibility is a known issue — plan for manual providers from the start when using drift
3. Verification gates add minimal time (~2 min) but catch integration issues — keep them for all phases
4. Progressive disclosure (AnimatedSize) is a clean pattern for conditional settings UI
### Cost Observations
- Model mix: orchestrator on opus, researchers/planners/executors/checkers on sonnet
- Total execution: ~1.3 hours for 13 plans across 4 phases
- Notable: Verification gates averaged 2 min — very efficient for the confidence they provide
---
## Milestone: v1.1 — Calendar & Polish
**Shipped:** 2026-03-16
**Phases:** 3 | **Plans:** 5
### What Was Built
- Horizontal 181-day calendar strip replacing the stacked daily plan HomeScreen
- CalendarDao with date-parameterized reactive Drift streams for day tasks and overdue tasks
- Task completion history bottom sheet with per-task reverse-chronological log
- Alphabetical, interval, and effort sort options with SharedPreferences persistence
- SortDropdown widget in both HomeScreen and TaskListScreen AppBars
### What Worked
- Phase dependency ordering (5 → 6+7 parallel-capable) meant calendar strip was stable before building features on top
- TDD red-green cycle continued smoothly — every plan had failing tests before implementation
- Auto-advance mode enabled rapid phase chaining with minimal manual intervention
- Existing patterns from v1.0 (DAO, provider, widget test) were reused directly — no new patterns invented unnecessarily
- CalendarStripController (VoidCallback holder) was simpler than GlobalKey approach — good architecture call
### What Was Inefficient
- StateProvider removal in Riverpod 3.x was discovered during execution rather than research — same category of issue as v1.0's riverpod_generator problem
- ROADMAP.md plan checkboxes still not auto-checked by executor (same bookkeeping gap as v1.0)
- Phase 5 plan split (data layer + UI) could have been a single plan given the small scope — overhead of 2 separate plans wasn't justified for ~13 min total
### Patterns Established
- CalendarStripController: VoidCallback holder for parent-to-child imperative scroll communication
- CalendarDayList state machine: first-run → celebration → emptyDay → hasTasks (5 states)
- In-memory sort via stream.map after DB stream emit — sort preference changes without re-querying
- SortPreferenceNotifier: sync default + async _loadPersisted() — matches ThemeNotifier pattern
- Nested Scaffold pattern for per-tab AppBars in StatefulShellRoute.indexedStack
### Key Lessons
1. Riverpod API surface changes (StateProvider removal) should be caught during phase research, not during execution — pattern repeats from v1.0
2. Plans under ~5 min execution can be merged into a single plan to reduce orchestration overhead
3. In-memory sort is the right approach when sort criteria don't affect DB queries — avoids re-streaming
4. Bottom sheets for one-shot modals (history) don't need dedicated Riverpod providers — ref.read() in ConsumerWidget is sufficient
### Cost Observations
- Model mix: orchestrator on opus, executors/checkers on sonnet
- Total execution: ~26 min for 5 plans across 3 phases
- Notable: Each plan averaged ~5 min — significantly faster than v1.0's ~6 min average due to established patterns
---
## Cross-Milestone Trends
### Process Evolution
| Milestone | Phases | Plans | Key Change |
|-----------|--------|-------|------------|
| v1.0 | 4 | 13 | Initial project — established all patterns |
| v1.1 | 3 | 5 | Reused v1.0 patterns — faster execution, auto-advance mode |
### Cumulative Quality
| Milestone | Tests | LOC (lib) | Key Metric |
|-----------|-------|-----------|------------|
| v1.0 | 89 | 7,773 | dart analyze clean, 0 issues |
| v1.1 | 108 | 9,051 | dart analyze clean, 0 issues |
### Top Lessons (Verified Across Milestones)
1. **Research must verify current package API signatures** — v1.0 hit riverpod_generator type incompatibility, v1.1 hit StateProvider removal. Same root cause: outdated API assumptions in plans.
2. **Established patterns compound** — v1.1 plans averaged ~5 min vs v1.0's ~6 min. Reusing DAO, provider, and test patterns eliminated design decisions.
3. **Verification gates are cheap insurance** — Consistently ~2 min per phase, caught regressions in both milestones.

View File

@@ -1,100 +1,97 @@
# Roadmap: HouseHoldKeaper # Roadmap: HouseHoldKeaper
## Overview ## Milestones
Four phases build the app bottom-up along its natural dependency chain. Phase 1 lays the technical foundation every subsequent phase relies on. Phase 2 delivers complete room and task management — the core scheduling loop. Phase 3 surfaces that data as the daily plan view (the primary user experience) and adds the cleanliness indicator. Phase 4 adds notifications and completes the v1 feature set. After Phase 3, the app is usable daily. After Phase 4, it is releasable. -**v1.0 MVP** — Phases 1-4 (shipped 2026-03-16)
-**v1.1 Calendar & Polish** — Phases 5-7 (shipped 2026-03-16)
- **v1.2 Polish & Task Management** — Phases 8-10 (in progress)
## Phases ## Phases
**Phase Numbering:** <details>
- Integer phases (1, 2, 3): Planned milestone work <summary>✅ v1.0 MVP (Phases 1-4) — SHIPPED 2026-03-16</summary>
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
Decimal phases appear between their surrounding integers in numeric order. - [x] Phase 1: Foundation (2/2 plans) — completed 2026-03-15
- [x] Phase 2: Rooms and Tasks (5/5 plans) — completed 2026-03-15
- [x] Phase 3: Daily Plan and Cleanliness (3/3 plans) — completed 2026-03-16
- [x] Phase 4: Notifications (3/3 plans) — completed 2026-03-16
- [x] **Phase 1: Foundation** - Project scaffold, database, state management, theme, and localization infrastructure (completed 2026-03-15) See `milestones/v1.0-ROADMAP.md` for full phase details.
- [x] **Phase 2: Rooms and Tasks** - Complete room CRUD, task CRUD with auto-scheduling, and bundled templates (completed 2026-03-15)
- [x] **Phase 3: Daily Plan and Cleanliness** - Primary daily plan screen with overdue/today/upcoming, cleanliness indicators per room (completed 2026-03-16) </details>
- [x] **Phase 4: Notifications** - Daily summary notification with configurable time and Android permission handling (completed 2026-03-16)
<details>
<summary>✅ v1.1 Calendar & Polish (Phases 5-7) — SHIPPED 2026-03-16</summary>
- [x] Phase 5: Calendar Strip (2/2 plans) — completed 2026-03-16
- [x] Phase 6: Task History (1/1 plans) — completed 2026-03-16
- [x] Phase 7: Task Sorting (2/2 plans) — completed 2026-03-16
See `milestones/v1.1-ROADMAP.md` for full phase details.
</details>
**v1.2 Polish & Task Management (Phases 8-10):**
- [x] **Phase 8: Task Delete** - Add smart delete action to tasks — hard delete if never completed, soft delete (deactivate) if completed at least once (completed 2026-03-18)
- [x] **Phase 9: Task Creation UX** - Rework the frequency picker from flat preset chips to an intuitive "Every N units" interface with quick-select shortcuts (completed 2026-03-18)
- [x] **Phase 10: Dead Code Cleanup** - Remove orphaned v1.0 daily plan files and verify no regressions (completed 2026-03-19)
## Phase Details ## Phase Details
### Phase 1: Foundation ### Phase 8: Task Delete
**Goal**: The app compiles, opens, and enforces correct architecture patterns — ready to receive features without accumulating technical debt **Goal**: Users can remove tasks they no longer need, with smart preservation of completion history for future statistics
**Depends on**: Nothing (first phase) **Depends on**: Phase 7 (v1.1 shipped — calendar, history, and sorting all in place)
**Requirements**: FOUND-01, FOUND-02, FOUND-03, FOUND-04, THEME-01, THEME-02 **Requirements**: DEL-01, DEL-02, DEL-03, DEL-04
**Success Criteria** (what must be TRUE): **Plans:** 2/2 plans complete
1. App launches on Android without errors and shows a bottom navigation bar with Home, Rooms, and Settings tabs
2. Light and dark themes work correctly and follow the system setting by default, using the calm Material 3 palette (muted greens, warm grays, gentle blues)
3. All UI strings are loaded from ARB localization files — no hardcoded German text in Dart code
4. The Drift database opens on first launch with schemaVersion 1 and the migration workflow is established (drift_dev make-migrations runs without errors)
5. riverpod_lint is active and flags ref.watch usage outside build() as an analysis error
**Plans**: 2 plans
Plans: Plans:
- [x] 01-01-PLAN.md — Scaffold Flutter project and build core infrastructure (database, providers, theme, localization) - [ ] 08-01-PLAN.md — Data layer: isActive column, schema migration, DAO filters and methods
- [x] 01-02-PLAN.md — Navigation shell, placeholder screens, Settings, and full app wiring - [ ] 08-02-PLAN.md — UI layer: delete button, confirmation dialog, smart delete provider
**Success Criteria** (what must be TRUE):
1. The task edit form has a clearly visible delete action (button or icon)
2. Deleting a task with zero completions removes it from the database entirely
3. Deleting a task with one or more completions sets it to inactive/archived — the task disappears from all active views (calendar, room task lists) but its completion records remain in the database
4. A confirmation dialog appears before any delete/archive action
5. The tasks table has an `isActive` (or equivalent) column, with all existing tasks defaulting to active via migration
### Phase 2: Rooms and Tasks ### Phase 9: Task Creation UX
**Goal**: Users can create and manage rooms and tasks, mark tasks done, and trust the app to schedule the next occurrence automatically **Goal**: Users can set any recurring frequency intuitively without hunting through a grid of preset chips — common frequencies are one tap away, custom intervals are freeform
**Depends on**: Phase 1 **Depends on**: Phase 8
**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 **Requirements**: TCX-01, TCX-02, TCX-03, TCX-04
**Success Criteria** (what must be TRUE): **Plans:** 1/1 plans complete
1. User can create a room with a name and icon, edit it, reorder rooms via drag-and-drop, and delete it (with confirmation that removes all associated tasks)
2. User can create a task in a room with name, description, frequency interval (daily through yearly and custom), and effort level; tasks can be edited and deleted with confirmation
3. When creating a room, user can select from bundled German-language task templates for the chosen room type (all 14 room types covered) and they are added to the room as tasks
4. User can mark a task done (tap or swipe), which records the completion and sets the next due date correctly based on the interval
5. Overdue tasks are visually highlighted with a distinct color or badge on room cards and in task lists; tasks within a room are sorted by due date by default
6. Each room card shows its name, icon, count of due tasks, and cleanliness indicator
**Plans**: 5 plans
Plans: Plans:
- [x] 02-01-PLAN.md — Data layer: Drift tables, migration v1->v2, DAOs, scheduling utility, domain models, templates, tests - [ ] 09-01-PLAN.md — Rework frequency picker: 4 shortcut chips + freeform "Every N units" picker
- [x] 02-02-PLAN.md — Room CRUD UI: 2-column card grid, room form, icon picker, drag-and-drop reorder, providers **Success Criteria** (what must be TRUE):
- [x] 02-03-PLAN.md — Task CRUD UI: task list, task row with completion, task form, overdue highlighting, providers 1. The frequency section presents a primary "Every [N] [unit]" picker where users can type a number and select days/weeks/months
- [x] 02-04-PLAN.md — Template selection: template picker bottom sheet, room type detection, integration with room creation 2. Common frequencies (daily, weekly, biweekly, monthly) are available as quick-select shortcuts that populate the picker
- [x] 02-05-PLAN.md — Visual and functional verification checkpoint 3. Any arbitrary interval is settable without a separate "Custom" mode — the picker is inherently freeform
4. All existing interval types and calendar-anchored scheduling behavior continue to work correctly (monthly/quarterly/yearly anchor memory)
5. Existing tasks load their current interval into the new picker correctly in edit mode
### Phase 3: Daily Plan and Cleanliness ### Phase 10: Dead Code Cleanup
**Goal**: Users can open the app and immediately see what needs doing today, act on tasks directly from the plan view, and see a room-level health indicator **Goal**: Remove orphaned v1.0 daily plan files that are no longer used after the calendar strip replacement, keeping the codebase clean
**Depends on**: Phase 2 **Depends on**: Phase 8 (cleanup after feature work is done)
**Requirements**: PLAN-01, PLAN-02, PLAN-03, PLAN-04, PLAN-05, PLAN-06, CLEAN-01 **Requirements**: CLN-01
**Success Criteria** (what must be TRUE): **Plans:** 1/1 plans complete
1. The Home tab shows today's tasks grouped by room, with a separate highlighted section at the top for overdue tasks
2. User can mark a task done directly from the daily plan view via swipe or checkbox without navigating to the room
3. User can see upcoming tasks (tomorrow and this week) from the daily plan screen
4. A progress indicator shows completed vs total tasks for today (e.g., "5 von 12 erledigt")
5. When no tasks are due, an encouraging "all clear" empty state is shown instead of an empty list
6. Each room card displays a cleanliness indicator derived from the ratio of overdue tasks to total tasks in that room
**Plans**: 3 plans
Plans: Plans:
- [x] 03-01-PLAN.md — Data layer: DailyPlanDao with cross-room join query, providers, and localization keys - [x] 10-01-PLAN.md — Delete 3 orphaned presentation files, remove DailyPlanState, verify zero regressions (completed 2026-03-19)
- [x] 03-02-PLAN.md — Daily plan UI: HomeScreen rewrite with progress card, task sections, animated completion, empty state
- [x] 03-03-PLAN.md — Visual and functional verification checkpoint
### Phase 4: Notifications
**Goal**: Users receive a daily summary notification reminding them of today's task count, and can control notification behavior from settings
**Depends on**: Phase 2
**Requirements**: NOTF-01, NOTF-02
**Success Criteria** (what must be TRUE): **Success Criteria** (what must be TRUE):
1. User receives one daily notification showing the count of tasks due today, scheduled at a configurable time 1. daily_plan_providers.dart, daily_plan_task_row.dart, and progress_card.dart are deleted
2. User can enable or disable notifications from the Settings tab, and the change takes effect immediately 2. DailyPlanDao is preserved (still used by notification service)
3. Notifications are correctly rescheduled after device reboot (RECEIVE_BOOT_COMPLETED receiver active) 3. All 108+ tests pass after cleanup
4. On Android API 33+, the app requests POST_NOTIFICATIONS permission at the appropriate moment and degrades gracefully if denied 4. `dart analyze` reports zero issues
**Plans**: 3 plans
Plans:
- [ ] 04-01-PLAN.md — Infrastructure: packages, Android config, NotificationService, NotificationSettingsNotifier, DAO queries, timezone init, tests
- [ ] 04-02-PLAN.md — Settings UI: Benachrichtigungen section with toggle, time picker, permission flow, scheduling wiring, tests
- [ ] 04-03-PLAN.md — Verification gate: dart analyze + full test suite + requirement confirmation
## Progress ## Progress
**Execution Order:** | Phase | Milestone | Plans Complete | Status | Completed |
Phases execute in numeric order: 1 -> 2 -> 3 -> 4 |-------|-----------|----------------|--------|-----------|
| 1. Foundation | v1.0 | 2/2 | Complete | 2026-03-15 |
Note: Phase 4 depends on Phase 2 (needs scheduling data) but can be developed in parallel with Phase 3. | 2. Rooms and Tasks | v1.0 | 5/5 | Complete | 2026-03-15 |
| 3. Daily Plan and Cleanliness | v1.0 | 3/3 | Complete | 2026-03-16 |
| Phase | Plans Complete | Status | Completed | | 4. Notifications | v1.0 | 3/3 | Complete | 2026-03-16 |
|-------|----------------|--------|-----------| | 5. Calendar Strip | v1.1 | 2/2 | Complete | 2026-03-16 |
| 1. Foundation | 2/2 | Complete | 2026-03-15 | | 6. Task History | v1.1 | 1/1 | Complete | 2026-03-16 |
| 2. Rooms and Tasks | 5/5 | Complete | 2026-03-15 | | 7. Task Sorting | v1.1 | 2/2 | Complete | 2026-03-16 |
| 3. Daily Plan and Cleanliness | 3/3 | Complete | 2026-03-16 | | 8. Task Delete | 2/2 | Complete | 2026-03-18 | - |
| 4. Notifications | 3/3 | Complete | 2026-03-16 | | 9. Task Creation UX | 1/1 | Complete | 2026-03-18 | - |
| 10. Dead Code Cleanup | v1.2 | Complete | 2026-03-19 | 2026-03-19 |

View File

@@ -2,15 +2,15 @@
gsd_state_version: 1.0 gsd_state_version: 1.0
milestone: v1.0 milestone: v1.0
milestone_name: milestone milestone_name: milestone
status: executing status: completed
stopped_at: Completed 04-03-PLAN.md (Phase 4 Verification Gate) stopped_at: Completed 10-dead-code-cleanup 10-01-PLAN.md
last_updated: "2026-03-16T14:20:25.850Z" last_updated: "2026-03-19T07:29:08.098Z"
last_activity: 2026-03-16Completed 04-01-PLAN.md (Notification infrastructure) last_activity: 2026-03-19Deleted orphaned v1.0 daily plan files and removed DailyPlanState
progress: progress:
total_phases: 4 total_phases: 3
completed_phases: 4 completed_phases: 3
total_plans: 13 total_plans: 4
completed_plans: 13 completed_plans: 4
percent: 100 percent: 100
--- ---
@@ -18,113 +18,60 @@ progress:
## Project Reference ## Project Reference
See: .planning/PROJECT.md (updated 2026-03-15) See: .planning/PROJECT.md (updated 2026-03-18)
**Core value:** Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it. **Core value:** Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it.
**Current focus:** Phase 4: Notifications (Phase 3 complete) **Current focus:** v1.2 Polish & Task Management — Phase 8: Task Delete
## Current Position ## Current Position
Phase: 4 of 4 (Notifications) Milestone: v1.2 Polish & Task Management
Plan: 1 of 2 in current phase -- COMPLETE Phase: 10 — Dead Code Cleanup (complete)
Status: Phase 4 in progress — plan 1 complete, plan 2 (Settings UI) pending Status: Completed 10-dead-code-cleanup 10-01-PLAN.md
Last activity: 2026-03-16Completed 04-01-PLAN.md (Notification infrastructure) Last activity: 2026-03-19Deleted orphaned v1.0 daily plan files and removed DailyPlanState
Progress: [██████████] 100% ```
Progress: [██████████] 100% (1/1 plans in phase 10)
```
## Performance Metrics ## Performance Metrics
**Velocity:** | Metric | v1.0 | v1.1 | v1.2 |
- Total plans completed: 10 |--------|------|------|------|
- Average duration: 6.1 min | Phases | 4 | 3 | 3 planned |
- Total execution time: 1.0 hours | Plans | 13 | 5 | TBD |
| LOC (lib) | 7,773 | 9,051 | TBD |
**By Phase:** | Tests | 89 | 108 | TBD |
| Phase 08-task-delete P01 | 9 | 2 tasks | 11 files |
| Phase | Plans | Total | Avg/Plan | | Phase 08-task-delete P02 | 2 | 2 tasks | 3 files |
|-------|-------|-------|----------| | Phase 09-task-creation-ux P01 | 2 | 1 tasks | 4 files |
| 1 - Foundation | 2 | 15 min | 7.5 min | | Phase 10-dead-code-cleanup P01 | 5 | 2 tasks | 4 files |
| 2 - Rooms and Tasks | 5 | 35 min | 7.0 min |
| 3 - Daily Plan and Cleanliness | 3 | 11 min | 3.7 min |
**Recent Trend:**
- Last 5 plans: 02-04 (3 min), 02-05 (1 min), 03-01 (5 min), 03-02 (4 min), 03-03 (2 min)
- Trend: Verification gates consistently fast (1-2 min)
*Updated after each plan completion*
| Phase 02 P01 | 8 | 2 tasks | 16 files |
| Phase 02 P02 | 11 | 2 tasks | 14 files |
| Phase 02 P03 | 12 | 2 tasks | 8 files |
| Phase 02 P04 | 3 | 2 tasks | 5 files |
| Phase 02 P05 | 1 | 1 task | 0 files |
| Phase 03 P01 | 5 | 2 tasks | 10 files |
| Phase 03 P02 | 4 | 2 tasks | 5 files |
| Phase 03 P03 | 2 | 2 tasks | 0 files |
| Phase 04-notifications P01 | 9 | 2 tasks | 11 files |
| Phase 04-notifications P02 | 5 | 2 tasks | 5 files |
| Phase 04-notifications P03 | 2 | 1 tasks | 0 files |
## Accumulated Context ## Accumulated Context
### Decisions ### Decisions
Decisions are logged in PROJECT.md Key Decisions table. Decisions archived to PROJECT.md Key Decisions table.
Recent decisions affecting current work: - [Phase 08-task-delete]: isActive uses BoolColumn.withDefault(true) so existing rows are automatically active after migration without backfill
- [Phase 08-task-delete]: Migration uses from==2 (not from<3) for addColumn to avoid duplicate-column error when createTable already includes isActive in current schema definition
- [Pre-phase]: Riverpod 3.3 requires Flutter 3.41+ — verify before scaffolding - [Phase 08-task-delete]: Migration tests updated to only test v1->v3 and v2->v3 paths since AppDatabase.schemaVersion=3 always migrates to v3
- [Pre-phase]: All due dates stored as date-only (calendar day), not DateTime — enforce from first migration - [Phase 08-task-delete]: smartDeleteTask kept separate from deleteTask to preserve existing hard-delete path for cascade/other uses
- [Pre-phase]: German-only UI for MVP; localization infrastructure (ARB + AppLocalizations) required from Phase 1 even with one locale - [Phase 08-task-delete]: Delete button placed after history section with divider, visible only in edit mode
- [Pre-phase]: riverpod_lint must be active before any feature code — catches ref.watch outside build() at analysis time - [Phase 09-task-creation-ux]: Picker is single source of truth: _resolveFrequency() reads from picker always; _ShortcutFrequency enum handles bidirectional sync via toPickerValues()/fromPickerValues()
- [Pre-phase]: drift_dev make-migrations workflow must be established in Phase 1 — recovery cost is data loss - [Phase 10-dead-code-cleanup]: DailyPlanDao kept in database.dart — still used by settings service; only the three presentation layer files were deleted
- [01-01]: Pinned drift/drift_dev to 2.31.0 for analyzer ^9.0.0 compatibility with riverpod_generator 4.0.3 - [Phase 10-dead-code-cleanup]: TaskWithRoom retained in daily_plan_models.dart — actively used by calendar_dao.dart, calendar_providers.dart, and related calendar files
- [01-01]: Generated Riverpod 3 provider named themeProvider (not themeNotifierProvider) per new naming convention
- [Phase 01-02]: Used themeProvider (Riverpod 3 naming) instead of themeNotifierProvider referenced in plan
- [02-01]: Scheduling functions are top-level pure functions with DateTime today parameter for testability
- [02-01]: Calendar-anchored intervals use anchorDay nullable field for month-clamping memory
- [02-01]: RoomWithStats computed via asyncMap on watchAllRooms stream, not a custom SQL join
- [02-01]: Templates stored as Dart const map for type safety, not JSON assets
- [02-01]: detectRoomType uses contains-based matching with alias map
- [Phase 02]: Scheduling functions are top-level pure functions with DateTime today parameter for testability
- [Phase 02]: Calendar-anchored intervals use anchorDay nullable field for month-clamping memory
- [Phase 02]: Templates stored as Dart const map for type safety, not JSON assets
- [02-02]: ReorderableBuilder<Widget> with typed onReorder callback for drag-and-drop grid
- [02-02]: Long-press context menu (bottom sheet) for edit/delete on room cards
- [02-02]: Provider override pattern in tests to decouple from database dependency
- [02-03]: tasksInRoomProvider defined as manual StreamProvider.family due to riverpod_generator InvalidTypeException with drift Task type
- [02-03]: Frequency selector uses ChoiceChip Wrap layout for 10 presets plus custom option
- [02-03]: TaskRow uses ListTile with middle-dot separator between relative date and frequency label
- [02-04]: Template picker uses StatefulWidget (not Consumer) receiving data via constructor
- [02-04]: Room creation navigates to /rooms/$roomId (context.go) instead of context.pop to show new room
- [02-04]: Calendar-anchored intervals set anchorDay to today's day-of-month; day-count intervals set null
- [02-05]: Auto-approved verification checkpoint: dart analyze clean, 59/59 tests passing, all Phase 2 code integrated
- [03-01]: DailyPlanDao uses innerJoin (not leftOuterJoin) since tasks always have a room
- [03-01]: watchCompletionsToday uses customSelect with readsFrom for proper stream invalidation
- [03-01]: dailyPlanProvider uses manual StreamProvider.autoDispose (not @riverpod) due to drift Task type issue
- [03-01]: Progress total = remaining overdue + remaining today + completedTodayCount for stable denominator
- [03-02]: Used stream-driven completion with local _completingTaskIds Set for animation instead of AnimatedList
- [03-02]: DailyPlanTaskRow is StatelessWidget (not ConsumerWidget) -- completion callback passed in from parent
- [03-02]: No-tasks empty state uses dailyPlanNoTasks key for clearer daily plan context messaging
- [03-03]: Phase 3 verification gate passed: dart analyze clean, 72/72 tests, all 7 requirements confirmed functional
- [Phase 04-01]: timezone constraint upgraded to ^0.11.0 — flutter_local_notifications v21 requires ^0.11.0, plan specified ^0.9.4
- [Phase 04-01]: flutter_local_notifications v21 uses named parameters in initialize() and zonedSchedule() — positional API removed in v20+
- [Phase 04-01]: Generated Riverpod 3 provider named notificationSettingsProvider (not notificationSettingsNotifierProvider) — consistent with themeProvider naming convention
- [Phase 04-01]: nextInstanceOf exposed as @visibleForTesting public method to enable TZ logic unit testing without native dispatch mocking
- [Phase Phase 04-02]: openNotificationSettings() not available in flutter_local_notifications v21 — simplified to informational SnackBar (no action button)
- [Phase Phase 04-02]: ConsumerStatefulWidget for SettingsScreen — async permission callbacks require mounted guards after every await
- [Phase 04-notifications]: Phase 4 verification gate passed: dart analyze --fatal-infos zero issues, 89/89 tests passing — all NOTF-01 and NOTF-02 requirements confirmed functional
### Pending Todos ### Pending Todos
None yet. None.
### Blockers/Concerns ### Blockers/Concerns
- ~~[Research]: Recurrence policy edge cases not fully specified~~ — **RESOLVED** in 2-CONTEXT.md: calendar-anchored intervals clamp to last day with anchor memory, day-count intervals roll forward. Next due from original due date. Catch-up skips to next future date. None.
- [Research]: Notification time configuration (user-adjustable vs hardcoded) not resolved. Decide before Phase 4 planning.
- ~~[Research]: First-launch template seeding UX (silent vs prompted) not resolved~~ — **RESOLVED** in 2-CONTEXT.md: post-creation prompt with all templates unchecked. Room type is optional, detected from name. Custom rooms skip templates entirely.
## Session Continuity ## Session Continuity
Last session: 2026-03-16T14:13:32.148Z Last session: 2026-03-19T00:05:00Z
Stopped at: Completed 04-03-PLAN.md (Phase 4 Verification Gate) Stopped at: Completed 10-dead-code-cleanup 10-01-PLAN.md
Resume file: None Resume file: None
Next action: Phase 10 complete

View File

@@ -5,7 +5,7 @@
"commit_docs": true, "commit_docs": true,
"model_profile": "balanced", "model_profile": "balanced",
"workflow": { "workflow": {
"research": true, "research": false,
"plan_check": true, "plan_check": true,
"verifier": true, "verifier": true,
"nyquist_validation": true, "nyquist_validation": true,

View File

@@ -0,0 +1,169 @@
# Requirements Archive: v1.0 MVP
**Archived:** 2026-03-16
**Status:** SHIPPED
For current requirements, see `.planning/REQUIREMENTS.md`.
---
# Requirements: HouseHoldKeaper
**Defined:** 2026-03-15
**Core Value:** Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it.
## v1 Requirements
Requirements for initial release. Each maps to roadmap phases.
### Room Management
- [x] **ROOM-01**: User can create a room with a name and an icon from a curated Material Icons set
- [x] **ROOM-02**: User can edit a room's name and icon
- [x] **ROOM-03**: User can delete a room with confirmation (cascades to associated tasks)
- [x] **ROOM-04**: User can reorder rooms via drag-and-drop on the rooms screen
- [x] **ROOM-05**: User can view all rooms as cards showing name, icon, due task count, and cleanliness indicator
### Task Management
- [x] **TASK-01**: User can create a task within a room with name, optional description, frequency interval, and effort level
- [x] **TASK-02**: User can edit a task's name, description, frequency interval, and effort level
- [x] **TASK-03**: User can delete a task with confirmation
- [x] **TASK-04**: User can set frequency interval from: daily, every 2 days, every 3 days, weekly, biweekly, monthly, every 2 months, quarterly, every 6 months, yearly, or custom (every N days)
- [x] **TASK-05**: User can set effort level (low / medium / high) on a task
- [x] **TASK-06**: User can sort tasks within a room by due date (default sort order)
- [x] **TASK-07**: User can mark a task as done via tap or swipe, which records a completion timestamp and auto-calculates the next due date based on the interval
- [x] **TASK-08**: Overdue tasks are visually highlighted with distinct color/badge on room cards and in task lists
### Task Templates
- [x] **TMPL-01**: When creating a room, user can select from bundled German-language task templates appropriate for that room type
- [x] **TMPL-02**: Preset room types with templates include: Küche, Badezimmer, Schlafzimmer, Wohnzimmer, Flur, Büro, Garage, Balkon, Waschküche, Keller, Kinderzimmer, Gästezimmer, Esszimmer, Garten/Außenbereich
### Daily Plan
- [x] **PLAN-01**: User sees all tasks due today grouped by room on the daily plan screen (primary/default screen)
- [x] **PLAN-02**: Overdue tasks appear in a separate highlighted section at the top of the daily plan
- [x] **PLAN-03**: User can preview upcoming tasks (tomorrow / this week)
- [x] **PLAN-04**: User can swipe-to-complete or tap checkbox to mark tasks done directly from the daily plan view
- [x] **PLAN-05**: User sees a progress indicator showing completed vs total tasks for today (e.g. "5 of 12 tasks done")
- [x] **PLAN-06**: When no tasks are due, user sees an encouraging "all clear" empty state
### Cleanliness Indicator
- [x] **CLEAN-01**: Each room card displays a cleanliness indicator derived from the ratio of overdue tasks to total tasks in that room
### Notifications
- [x] **NOTF-01**: User receives a daily summary notification showing today's task count at a configurable time
- [x] **NOTF-02**: User can enable/disable notifications in settings
### Theme & UI
- [x] **THEME-01**: App supports light and dark themes, following the system setting by default
- [x] **THEME-02**: App uses a calm Material 3 palette with muted greens, warm grays, and gentle blues
### Foundation
- [x] **FOUND-01**: App uses Drift for local SQLite storage with proper schema migration workflow
- [x] **FOUND-02**: App uses Riverpod 3 for state management with code generation
- [x] **FOUND-03**: App uses localization infrastructure (ARB files + AppLocalizations) with German locale, even though only one language ships in v1
- [x] **FOUND-04**: Bottom navigation with tabs: Home (Daily Plan), Rooms, Settings
## v2 Requirements
Deferred to future release. Tracked but not in current roadmap.
### v1.1 — Near-Term
- **EXPORT-01**: User can export all data as JSON file
- **EXPORT-02**: User can import data from a JSON file
- **I18N-01**: App supports English as a second language
- **PHOTO-01**: User can add a cover photo to a room from camera or gallery
- **HIST-01**: User can view a completion history log per task (scrollable timeline of completion dates)
- **SORT-01**: User can sort tasks by alphabetical order, interval length, or effort level (in addition to due date)
### v1.2 — Medium-Term
- **PROJ-01**: User can create one-time organization projects with sub-task steps
- **PROJ-02**: User can attach before/after photos to a project
- **PROF-01**: User can create named local profiles for household members
- **PROF-02**: User can assign tasks to one or more profiles
- **PROF-03**: User can enable task rotation (round-robin) for shared recurring tasks
- **PROF-04**: User can filter the daily plan view by profile ("My tasks" vs "All tasks")
- **WIDG-01**: Home screen widget showing today's due tasks and overdue count
- **CAL-01**: User can view a weekly overview with task load per day
- **CAL-02**: User can view a monthly calendar heatmap showing task density
- **VAC-01**: User can pause/freeze all task due dates during vacation and resume on return
### v2.0 — Future
- **STAT-01**: User can view completion rate (% on time this week/month)
- **STAT-02**: User can view streak of consecutive days with all tasks completed
- **STAT-03**: User can view per-room health scores over time
- **ONBRD-01**: First-launch wizard walks user through creating first room and adding tasks
- **COLOR-01**: User can pick a custom accent color for the app theme
- **SYNC-01**: User can optionally sync data via self-hosted infrastructure
- **TABLET-01**: App provides a tablet-optimized layout with adaptive breakpoints
- **NOTF-03**: Optional evening nudge notification if overdue tasks remain
## Out of Scope
Explicitly excluded. Documented to prevent scope creep.
| Feature | Reason |
|---------|--------|
| User accounts & cloud sync | Local-only by design — zero backend, zero data leaves device |
| Leaderboards & points ranking | Anti-feature — gamification causes app burnout and embeds unequal labor dynamics |
| Subscription model / in-app purchases | Free forever by design — paywalls are the #2 complaint in chore app reviews |
| Family profile sharing across devices | Single-device app; cross-device requires cloud infrastructure |
| AI-powered task suggestions | Requires network/ML; overkill for personal app with curated templates |
| Per-task push notifications | Causes notification fatigue; daily summary is more effective for habit formation |
| Focus timer / Pomodoro | Not a productivity timer app; out of domain |
| Firebase or any Google cloud services | Contradicts local-first, privacy-first design |
## Traceability
Which phases cover which requirements. Updated during roadmap creation.
| Requirement | Phase | Status |
|-------------|-------|--------|
| FOUND-01 | Phase 1: Foundation | Complete |
| FOUND-02 | Phase 1: Foundation | Complete |
| FOUND-03 | Phase 1: Foundation | Complete |
| FOUND-04 | Phase 1: Foundation | Complete |
| THEME-01 | Phase 1: Foundation | Complete |
| THEME-02 | Phase 1: Foundation | Complete |
| ROOM-01 | Phase 2: Rooms and Tasks | Complete |
| ROOM-02 | Phase 2: Rooms and Tasks | Complete |
| ROOM-03 | Phase 2: Rooms and Tasks | Complete |
| ROOM-04 | Phase 2: Rooms and Tasks | Complete |
| ROOM-05 | Phase 2: Rooms and Tasks | Complete |
| TASK-01 | Phase 2: Rooms and Tasks | Complete |
| TASK-02 | Phase 2: Rooms and Tasks | Complete |
| TASK-03 | Phase 2: Rooms and Tasks | Complete |
| TASK-04 | Phase 2: Rooms and Tasks | Complete |
| TASK-05 | Phase 2: Rooms and Tasks | Complete |
| TASK-06 | Phase 2: Rooms and Tasks | Complete |
| TASK-07 | Phase 2: Rooms and Tasks | Complete |
| TASK-08 | Phase 2: Rooms and Tasks | Complete |
| TMPL-01 | Phase 2: Rooms and Tasks | Complete |
| TMPL-02 | Phase 2: Rooms and Tasks | Complete |
| PLAN-01 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-02 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-03 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-04 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-05 | Phase 3: Daily Plan and Cleanliness | Complete |
| PLAN-06 | Phase 3: Daily Plan and Cleanliness | Complete |
| CLEAN-01 | Phase 3: Daily Plan and Cleanliness | Complete |
| NOTF-01 | Phase 4: Notifications | Complete |
| NOTF-02 | Phase 4: Notifications | Complete |
**Coverage:**
- v1 requirements: 30 total
- Mapped to phases: 30
- Unmapped: 0
---
*Requirements defined: 2026-03-15*
*Last updated: 2026-03-15 after roadmap creation*

View File

@@ -0,0 +1,100 @@
# Roadmap: HouseHoldKeaper
## Overview
Four phases build the app bottom-up along its natural dependency chain. Phase 1 lays the technical foundation every subsequent phase relies on. Phase 2 delivers complete room and task management — the core scheduling loop. Phase 3 surfaces that data as the daily plan view (the primary user experience) and adds the cleanliness indicator. Phase 4 adds notifications and completes the v1 feature set. After Phase 3, the app is usable daily. After Phase 4, it is releasable.
## Phases
**Phase Numbering:**
- Integer phases (1, 2, 3): Planned milestone work
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
Decimal phases appear between their surrounding integers in numeric order.
- [x] **Phase 1: Foundation** - Project scaffold, database, state management, theme, and localization infrastructure (completed 2026-03-15)
- [x] **Phase 2: Rooms and Tasks** - Complete room CRUD, task CRUD with auto-scheduling, and bundled templates (completed 2026-03-15)
- [x] **Phase 3: Daily Plan and Cleanliness** - Primary daily plan screen with overdue/today/upcoming, cleanliness indicators per room (completed 2026-03-16)
- [x] **Phase 4: Notifications** - Daily summary notification with configurable time and Android permission handling (completed 2026-03-16)
## Phase Details
### Phase 1: Foundation
**Goal**: The app compiles, opens, and enforces correct architecture patterns — ready to receive features without accumulating technical debt
**Depends on**: Nothing (first phase)
**Requirements**: FOUND-01, FOUND-02, FOUND-03, FOUND-04, THEME-01, THEME-02
**Success Criteria** (what must be TRUE):
1. App launches on Android without errors and shows a bottom navigation bar with Home, Rooms, and Settings tabs
2. Light and dark themes work correctly and follow the system setting by default, using the calm Material 3 palette (muted greens, warm grays, gentle blues)
3. All UI strings are loaded from ARB localization files — no hardcoded German text in Dart code
4. The Drift database opens on first launch with schemaVersion 1 and the migration workflow is established (drift_dev make-migrations runs without errors)
5. riverpod_lint is active and flags ref.watch usage outside build() as an analysis error
**Plans**: 2 plans
Plans:
- [x] 01-01-PLAN.md — Scaffold Flutter project and build core infrastructure (database, providers, theme, localization)
- [x] 01-02-PLAN.md — Navigation shell, placeholder screens, Settings, and full app wiring
### Phase 2: Rooms and Tasks
**Goal**: Users can create and manage rooms and tasks, mark tasks done, and trust the app to schedule the next occurrence automatically
**Depends on**: Phase 1
**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
**Success Criteria** (what must be TRUE):
1. User can create a room with a name and icon, edit it, reorder rooms via drag-and-drop, and delete it (with confirmation that removes all associated tasks)
2. User can create a task in a room with name, description, frequency interval (daily through yearly and custom), and effort level; tasks can be edited and deleted with confirmation
3. When creating a room, user can select from bundled German-language task templates for the chosen room type (all 14 room types covered) and they are added to the room as tasks
4. User can mark a task done (tap or swipe), which records the completion and sets the next due date correctly based on the interval
5. Overdue tasks are visually highlighted with a distinct color or badge on room cards and in task lists; tasks within a room are sorted by due date by default
6. Each room card shows its name, icon, count of due tasks, and cleanliness indicator
**Plans**: 5 plans
Plans:
- [x] 02-01-PLAN.md — Data layer: Drift tables, migration v1->v2, DAOs, scheduling utility, domain models, templates, tests
- [x] 02-02-PLAN.md — Room CRUD UI: 2-column card grid, room form, icon picker, drag-and-drop reorder, providers
- [x] 02-03-PLAN.md — Task CRUD UI: task list, task row with completion, task form, overdue highlighting, providers
- [x] 02-04-PLAN.md — Template selection: template picker bottom sheet, room type detection, integration with room creation
- [x] 02-05-PLAN.md — Visual and functional verification checkpoint
### Phase 3: Daily Plan and Cleanliness
**Goal**: Users can open the app and immediately see what needs doing today, act on tasks directly from the plan view, and see a room-level health indicator
**Depends on**: Phase 2
**Requirements**: PLAN-01, PLAN-02, PLAN-03, PLAN-04, PLAN-05, PLAN-06, CLEAN-01
**Success Criteria** (what must be TRUE):
1. The Home tab shows today's tasks grouped by room, with a separate highlighted section at the top for overdue tasks
2. User can mark a task done directly from the daily plan view via swipe or checkbox without navigating to the room
3. User can see upcoming tasks (tomorrow and this week) from the daily plan screen
4. A progress indicator shows completed vs total tasks for today (e.g., "5 von 12 erledigt")
5. When no tasks are due, an encouraging "all clear" empty state is shown instead of an empty list
6. Each room card displays a cleanliness indicator derived from the ratio of overdue tasks to total tasks in that room
**Plans**: 3 plans
Plans:
- [x] 03-01-PLAN.md — Data layer: DailyPlanDao with cross-room join query, providers, and localization keys
- [x] 03-02-PLAN.md — Daily plan UI: HomeScreen rewrite with progress card, task sections, animated completion, empty state
- [x] 03-03-PLAN.md — Visual and functional verification checkpoint
### Phase 4: Notifications
**Goal**: Users receive a daily summary notification reminding them of today's task count, and can control notification behavior from settings
**Depends on**: Phase 2
**Requirements**: NOTF-01, NOTF-02
**Success Criteria** (what must be TRUE):
1. User receives one daily notification showing the count of tasks due today, scheduled at a configurable time
2. User can enable or disable notifications from the Settings tab, and the change takes effect immediately
3. Notifications are correctly rescheduled after device reboot (RECEIVE_BOOT_COMPLETED receiver active)
4. On Android API 33+, the app requests POST_NOTIFICATIONS permission at the appropriate moment and degrades gracefully if denied
**Plans**: 3 plans
Plans:
- [ ] 04-01-PLAN.md — Infrastructure: packages, Android config, NotificationService, NotificationSettingsNotifier, DAO queries, timezone init, tests
- [ ] 04-02-PLAN.md — Settings UI: Benachrichtigungen section with toggle, time picker, permission flow, scheduling wiring, tests
- [ ] 04-03-PLAN.md — Verification gate: dart analyze + full test suite + requirement confirmation
## Progress
**Execution Order:**
Phases execute in numeric order: 1 -> 2 -> 3 -> 4
Note: Phase 4 depends on Phase 2 (needs scheduling data) but can be developed in parallel with Phase 3.
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Foundation | 2/2 | Complete | 2026-03-15 |
| 2. Rooms and Tasks | 5/5 | Complete | 2026-03-15 |
| 3. Daily Plan and Cleanliness | 3/3 | Complete | 2026-03-16 |
| 4. Notifications | 3/3 | Complete | 2026-03-16 |

View File

@@ -0,0 +1,90 @@
# Requirements Archive: v1.1 Calendar & Polish
**Archived:** 2026-03-16
**Status:** SHIPPED
For current requirements, see `.planning/REQUIREMENTS.md`.
---
# Requirements: HouseHoldKeaper
**Defined:** 2026-03-16
**Core Value:** Users can see what needs doing today, mark it done, and trust the app to schedule the next occurrence — without thinking about it.
## v1.1 Requirements
Requirements for milestone v1.1 Calendar & Polish. Each maps to roadmap phases.
### Calendar UI
- [x] **CAL-01**: User sees a horizontal scrollable date-strip with day abbreviation (Mo, Di...) and date number per card
- [x] **CAL-02**: User can tap a day card to see that day's tasks in a list below the strip
- [x] **CAL-03**: User sees a subtle color shift at month boundaries for visual orientation
- [x] **CAL-04**: Calendar strip auto-scrolls to today on app launch
- [x] **CAL-05**: Undone tasks carry over to the next day with a red/orange color accent marking them as overdue
### Task History
- [x] **HIST-01**: Each task completion is recorded with a timestamp
- [x] **HIST-02**: User can view past completion dates for any individual task
### Task Sorting
- [x] **SORT-01**: User can sort tasks alphabetically
- [x] **SORT-02**: User can sort tasks by frequency interval
- [x] **SORT-03**: User can sort tasks by effort level
## Future Requirements
Deferred to future release. Tracked but not in current roadmap.
### Data
- **DATA-01**: User can export all data as JSON
- **DATA-02**: User can import data from JSON backup
### Localization
- **LOC-01**: User can switch UI language to English
### Rooms
- **ROOM-01**: User can set a cover photo for a room from camera or gallery
## Out of Scope
Explicitly excluded. Documented to prevent scope creep.
| Feature | Reason |
|---------|--------|
| Weekly/monthly calendar views | Overcomplicates UI — date strip is sufficient for task app |
| Drag tasks between days | Not needed — tasks auto-schedule based on frequency |
| Calendar sync (Google/Apple) | Contradicts local-first, offline-only design |
| Task statistics/charts | Deferred to v2.0 — history log is the foundation |
## Traceability
Which phases cover which requirements. Updated during roadmap creation.
| Requirement | Phase | Status |
|-------------|-------|--------|
| CAL-01 | Phase 5 | Complete |
| CAL-02 | Phase 5 | Complete |
| CAL-03 | Phase 5 | Complete |
| CAL-04 | Phase 5 | Complete |
| CAL-05 | Phase 5 | Complete |
| HIST-01 | Phase 6 | Complete |
| HIST-02 | Phase 6 | Complete |
| SORT-01 | Phase 7 | Complete |
| SORT-02 | Phase 7 | Complete |
| SORT-03 | Phase 7 | Complete |
**Coverage:**
- v1.1 requirements: 10 total
- Mapped to phases: 10
- Unmapped: 0
---
*Requirements defined: 2026-03-16*
*Last updated: 2026-03-16 after roadmap creation (phases 5-7)*

View File

@@ -0,0 +1,81 @@
# Roadmap: HouseHoldKeaper
## Milestones
- **v1.0 MVP** — Phases 1-4 (shipped 2026-03-16)
- **v1.1 Calendar & Polish** — Phases 5-7 (in progress)
## Phases
<details>
<summary>v1.0 MVP (Phases 1-4) — SHIPPED 2026-03-16</summary>
- [x] Phase 1: Foundation (2/2 plans) — completed 2026-03-15
- [x] Phase 2: Rooms and Tasks (5/5 plans) — completed 2026-03-15
- [x] Phase 3: Daily Plan and Cleanliness (3/3 plans) — completed 2026-03-16
- [x] Phase 4: Notifications (3/3 plans) — completed 2026-03-16
See `milestones/v1.0-ROADMAP.md` for full phase details.
</details>
**v1.1 Calendar & Polish (Phases 5-7):**
- [x] **Phase 5: Calendar Strip** - Replace the stacked daily plan home screen with a horizontal scrollable date-strip and day-task list (completed 2026-03-16)
- [x] **Phase 6: Task History** - Record every task completion with a timestamp and expose a per-task history view (completed 2026-03-16)
- [x] **Phase 7: Task Sorting** - Add alphabetical, interval, and effort sort options to task lists (completed 2026-03-16)
## Phase Details
### Phase 5: Calendar Strip
**Goal**: Users navigate their tasks through a horizontal date-strip that replaces the stacked daily plan, seeing today's tasks by default and any day's tasks on tap
**Depends on**: Phase 4 (v1.0 shipped — all data layer and scheduling in place)
**Requirements**: CAL-01, CAL-02, CAL-03, CAL-04, CAL-05
**Success Criteria** (what must be TRUE):
1. The home screen shows a horizontal scrollable strip of day cards, each displaying the German day abbreviation (Mo, Di, Mi...) and the date number
2. Tapping any day card updates the task list below the strip to show that day's tasks, with the selected card visually highlighted
3. On app launch the strip auto-scrolls so today's card is centered and selected by default
4. When two adjacent day cards span a month boundary, a subtle color shift or divider makes the boundary visible without extra chrome
5. Tasks that were not completed on their due date appear in subsequent days' lists with a red/orange accent marking them as overdue
**Plans:** 2/2 plans complete
Plans:
- [ ] 05-01-PLAN.md — Data layer: CalendarDao, CalendarDayState model, Riverpod providers, localization, DAO tests
- [ ] 05-02-PLAN.md — UI: CalendarStrip, CalendarDayList, CalendarTaskRow widgets, HomeScreen replacement
### Phase 6: Task History
**Goal**: Users can see exactly when each task was completed in the past, building trust that the scheduling loop is working correctly
**Depends on**: Phase 5
**Requirements**: HIST-01, HIST-02
**Success Criteria** (what must be TRUE):
1. Every task completion (tap done in any view) is recorded in the database with a precise timestamp — data persists across app restarts
2. From a task's detail or context menu the user can open a history view listing all past completion dates for that task in reverse-chronological order
3. The history view shows a meaningful empty state if the task has never been completed
**Plans:** 1/1 plans complete
Plans:
- [ ] 06-01-PLAN.md — DAO query + history bottom sheet + TaskFormScreen integration + CalendarTaskRow navigation
### Phase 7: Task Sorting
**Goal**: Users can reorder task lists by the dimension most useful to them — name, how often the task recurs, or how much effort it requires
**Depends on**: Phase 5
**Requirements**: SORT-01, SORT-02, SORT-03
**Success Criteria** (what must be TRUE):
1. A sort control (dropdown, segmented button, or similar) is visible on task list screens and persists the chosen sort across app restarts
2. Selecting alphabetical sort orders tasks A-Z by name within the visible list
3. Selecting interval sort orders tasks from most-frequent (daily) to least-frequent (yearly/custom) intervals
4. Selecting effort sort orders tasks from lowest effort to highest effort level
**Plans:** 2/2 plans complete
Plans:
- [ ] 07-01-PLAN.md — Sort model, persistence notifier, localization, provider integration
- [ ] 07-02-PLAN.md — Sort dropdown widget, HomeScreen AppBar, TaskListScreen integration, tests
## Progress
| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
| 1. Foundation | v1.0 | 2/2 | Complete | 2026-03-15 |
| 2. Rooms and Tasks | v1.0 | 5/5 | Complete | 2026-03-15 |
| 3. Daily Plan and Cleanliness | v1.0 | 3/3 | Complete | 2026-03-16 |
| 4. Notifications | v1.0 | 3/3 | Complete | 2026-03-16 |
| 5. Calendar Strip | 2/2 | Complete | 2026-03-16 | - |
| 6. Task History | 1/1 | Complete | 2026-03-16 | - |
| 7. Task Sorting | 2/2 | Complete | 2026-03-16 | - |

View File

@@ -0,0 +1,262 @@
---
phase: 05-calendar-strip
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- lib/features/home/data/calendar_dao.dart
- lib/features/home/data/calendar_dao.g.dart
- lib/features/home/domain/calendar_models.dart
- lib/features/home/presentation/calendar_providers.dart
- lib/core/database/database.dart
- lib/core/database/database.g.dart
- lib/l10n/app_de.arb
- lib/l10n/app_localizations_de.dart
- lib/l10n/app_localizations.dart
- test/features/home/data/calendar_dao_test.dart
autonomous: true
requirements:
- CAL-02
- CAL-05
must_haves:
truths:
- "Querying tasks for any arbitrary date returns exactly the tasks whose nextDueDate falls on that day"
- "Querying overdue tasks for today returns all tasks whose nextDueDate is strictly before today"
- "Querying a future date returns only tasks due that day, no overdue carry-over"
- "CalendarState model holds selectedDate, overdue tasks, and day tasks as separate lists"
- "Localization strings for calendar UI exist in ARB and generated files"
artifacts:
- path: "lib/features/home/data/calendar_dao.dart"
provides: "Date-parameterized task queries"
exports: ["CalendarDao"]
- path: "lib/features/home/domain/calendar_models.dart"
provides: "CalendarState and reuse of TaskWithRoom"
exports: ["CalendarState"]
- path: "lib/features/home/presentation/calendar_providers.dart"
provides: "Riverpod provider for calendar state"
exports: ["calendarProvider", "selectedDateProvider"]
- path: "test/features/home/data/calendar_dao_test.dart"
provides: "DAO unit tests"
min_lines: 50
key_links:
- from: "lib/features/home/data/calendar_dao.dart"
to: "lib/core/database/database.dart"
via: "DAO registered in @DriftDatabase annotation"
pattern: "CalendarDao"
- from: "lib/features/home/presentation/calendar_providers.dart"
to: "lib/features/home/data/calendar_dao.dart"
via: "Provider reads CalendarDao from AppDatabase"
pattern: "db\\.calendarDao"
---
<objective>
Create the data layer, domain models, Riverpod providers, and localization strings for the calendar strip feature.
Purpose: The calendar strip UI (Plan 02) needs a data foundation that can answer "what tasks are due on date X?" and "what tasks are overdue relative to today?" without the old overdue/today/tomorrow bucketing. This plan builds that foundation and tests it.
Output: CalendarDao with date-parameterized queries, CalendarState model, Riverpod providers (selectedDateProvider + calendarProvider), new l10n strings, DAO unit 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/05-calendar-strip/5-CONTEXT.md
<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
From lib/core/database/database.dart:
```dart
// Tables: Rooms, Tasks, TaskCompletions
// Existing DAOs: RoomsDao, TasksDao, DailyPlanDao
// CalendarDao must be added to the @DriftDatabase annotation daos list
// and imported at the top of database.dart
@DriftDatabase(
tables: [Rooms, Tasks, TaskCompletions],
daos: [RoomsDao, TasksDao, DailyPlanDao], // ADD CalendarDao here
)
class AppDatabase extends _$AppDatabase { ... }
```
From lib/features/home/domain/daily_plan_models.dart:
```dart
class TaskWithRoom {
final Task task;
final String roomName;
final int roomId;
const TaskWithRoom({required this.task, required this.roomName, required this.roomId});
}
```
From lib/features/home/presentation/daily_plan_providers.dart:
```dart
// Pattern to follow: StreamProvider.autoDispose, manual (not @riverpod)
// because of drift's generated Task type
final dailyPlanProvider = StreamProvider.autoDispose<DailyPlanState>((ref) {
final db = ref.watch(appDatabaseProvider);
...
});
```
From lib/features/home/data/daily_plan_dao.dart:
```dart
// Pattern: @DriftAccessor with tables, extends DatabaseAccessor<AppDatabase>
// Uses query.watch() for reactive streams
@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])
class DailyPlanDao extends DatabaseAccessor<AppDatabase> with _$DailyPlanDaoMixin { ... }
```
From lib/core/providers/database_provider.dart:
```dart
// appDatabaseProvider gives access to the database singleton
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create CalendarDao with date-parameterized queries and tests</name>
<files>
lib/features/home/data/calendar_dao.dart,
lib/core/database/database.dart,
test/features/home/data/calendar_dao_test.dart
</files>
<behavior>
- watchTasksForDate(date): returns tasks whose nextDueDate falls on the given calendar day (same year/month/day), joined with room name, sorted by task name alphabetically
- watchOverdueTasks(referenceDate): returns tasks whose nextDueDate is strictly before referenceDate (start of day), joined with room name, sorted by nextDueDate ascending
- watchTasksForDate for a date with no tasks returns empty list
- watchOverdueTasks returns empty when no tasks are overdue
- watchOverdueTasks does NOT include tasks due on the referenceDate itself
- watchTasksForDate for a past date returns only tasks originally due that day (does NOT include overdue carry-over)
</behavior>
<action>
1. Create `lib/features/home/data/calendar_dao.dart`:
- Class `CalendarDao` extends `DatabaseAccessor<AppDatabase>` with `_$CalendarDaoMixin`
- Annotated `@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])`
- `part 'calendar_dao.g.dart';`
- Method `Stream<List<TaskWithRoom>> watchTasksForDate(DateTime date)`:
Compute startOfDay and endOfDay (startOfDay + 1 day). Join tasks with rooms. Filter `tasks.nextDueDate >= startOfDay AND tasks.nextDueDate < endOfDay`. Order by `tasks.name` ascending. Map to `TaskWithRoom`.
- Method `Stream<List<TaskWithRoom>> watchOverdueTasks(DateTime referenceDate)`:
Compute startOfReferenceDay. Join tasks with rooms. Filter `tasks.nextDueDate < startOfReferenceDay`. Order by `tasks.nextDueDate` ascending. Map to `TaskWithRoom`.
- Import `daily_plan_models.dart` for `TaskWithRoom` (reuse, don't duplicate).
2. Register CalendarDao in `lib/core/database/database.dart`:
- Add import: `import '../../features/home/data/calendar_dao.dart';`
- Add `CalendarDao` to the `daos:` list in `@DriftDatabase`
3. Run `dart run build_runner build --delete-conflicting-outputs` to generate `calendar_dao.g.dart` and updated `database.g.dart`.
4. Write `test/features/home/data/calendar_dao_test.dart` following the pattern in `test/features/home/data/daily_plan_dao_test.dart`:
- Use in-memory database: `AppDatabase(NativeDatabase.memory())`
- Create test rooms in setUp
- Test group for watchTasksForDate:
- Empty when no tasks
- Returns only tasks due on the queried date (not before, not after)
- Returns tasks from multiple rooms
- Sorted alphabetically by name
- Test group for watchOverdueTasks:
- Empty when no overdue tasks
- Returns tasks due before reference date
- Does NOT include tasks due ON the reference date
- Sorted by nextDueDate ascending
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/home/data/calendar_dao_test.dart</automated>
</verify>
<done>CalendarDao registered in AppDatabase, both query methods return correct results for arbitrary dates, all DAO tests pass</done>
</task>
<task type="auto">
<name>Task 2: Create CalendarState model, Riverpod providers, and localization strings</name>
<files>
lib/features/home/domain/calendar_models.dart,
lib/features/home/presentation/calendar_providers.dart,
lib/l10n/app_de.arb
</files>
<action>
1. Create `lib/features/home/domain/calendar_models.dart`:
```dart
import 'package:household_keeper/features/home/domain/daily_plan_models.dart';
/// State for the calendar day view: tasks for the selected date + overdue tasks.
class CalendarDayState {
final DateTime selectedDate;
final List<TaskWithRoom> dayTasks;
final List<TaskWithRoom> overdueTasks;
const CalendarDayState({
required this.selectedDate,
required this.dayTasks,
required this.overdueTasks,
});
/// True when viewing today and all tasks (day + overdue) have been completed
/// (lists are empty but completions exist). Determined by the UI layer.
bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty;
}
```
2. Create `lib/features/home/presentation/calendar_providers.dart`:
- Import Riverpod, database_provider, calendar_dao, calendar_models, daily_plan_models
- `final selectedDateProvider = StateProvider<DateTime>((ref) { final now = DateTime.now(); return DateTime(now.year, now.month, now.day); });`
This is NOT autoDispose -- the selected date persists as long as the app is alive (resets on restart naturally).
- `final calendarDayProvider = StreamProvider.autoDispose<CalendarDayState>((ref) { ... });`
Manual definition (not @riverpod) following dailyPlanProvider pattern.
Reads `selectedDateProvider` to get the current date.
Reads `appDatabaseProvider` to get the DB.
Determines if selectedDate is today: `isToday = selectedDate == DateTime(now.year, now.month, now.day)`.
Determines if selectedDate is in the future: `isFuture = selectedDate.isAfter(today)`.
Watches `db.calendarDao.watchTasksForDate(selectedDate)`.
For overdue: if `isToday`, also watch `db.calendarDao.watchOverdueTasks(selectedDate)`.
If viewing a past date or future date, overdueTasks = empty.
Per user decision: "When viewing past days: show what was due that day. When viewing future days: show only tasks due that day, no overdue carry-over."
Combine both streams using `Rx.combineLatest2` or simply use `asyncMap` on the day tasks stream and fetch overdue as a secondary query.
Implementation approach: Use the dayTasks stream as the primary, and inside asyncMap call the overdue stream's `.first` when isToday. This keeps it simple and follows the existing `dailyPlanProvider` pattern of `stream.asyncMap()`.
3. Add new l10n strings to `lib/l10n/app_de.arb` (add before the closing `}`):
- `"calendarNoTasks": "Keine Aufgaben"` — shown when a day has no tasks at all
- `"calendarAllDone": "Alles erledigt!"` — celebration when all tasks for a day are done
- `"calendarOverdueSection": "Uberfaellig"` — No, reuse existing `dailyPlanSectionOverdue` ("Uberfaellig") for the overdue section header
- `"calendarTodayButton": "Heute"` — floating today button label
Actually, we can reuse `dailyPlanSectionOverdue` for the overdue header, `dailyPlanNoTasks` for no-tasks-at-all, and `dailyPlanAllClearTitle`/`dailyPlanAllClearMessage` for celebration. The only truly new string needed is for the Today button:
- Add `"calendarTodayButton": "Heute"` to the ARB file
4. Run `flutter gen-l10n` to regenerate localization files.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos</automated>
</verify>
<done>CalendarDayState model exists with selectedDate/dayTasks/overdueTasks fields. selectedDateProvider and calendarDayProvider are defined. calendarDayProvider returns overdue tasks only when viewing today. New l10n string "calendarTodayButton" exists. No analysis errors.</done>
</task>
</tasks>
<verification>
- `flutter test test/features/home/data/calendar_dao_test.dart` — all DAO tests pass
- `flutter analyze --no-fatal-infos` — no errors in new or modified files
- `flutter test` — full test suite still passes (existing tests not broken by database.dart changes)
</verification>
<success_criteria>
- CalendarDao is registered in AppDatabase and has two working query methods
- CalendarDayState model correctly separates day tasks from overdue tasks
- calendarDayProvider returns overdue only for today, not for past/future dates
- All existing tests still pass after database.dart modification
- New DAO tests cover core query behaviors
</success_criteria>
<output>
After completion, create `.planning/phases/05-calendar-strip/05-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,142 @@
---
phase: 05-calendar-strip
plan: 01
subsystem: database
tags: [drift, riverpod, dart, flutter, localization, tdd]
# Dependency graph
requires: []
provides:
- CalendarDao with watchTasksForDate and watchOverdueTasks date-parameterized queries
- CalendarDayState domain model with selectedDate/dayTasks/overdueTasks
- selectedDateProvider (NotifierProvider, persists while app is alive)
- calendarDayProvider (StreamProvider.autoDispose, overdue only for today)
- calendarTodayButton l10n string in ARB and generated dart files
- 11 DAO unit tests covering all query behaviors
affects:
- 05-calendar-strip plan 02 (calendar strip UI uses these providers and state model)
# Tech tracking
tech-stack:
added: []
patterns:
- "CalendarDao follows @DriftAccessor pattern with DatabaseAccessor<AppDatabase>"
- "Manual NotifierProvider<SelectedDateNotifier, DateTime> instead of @riverpod (Riverpod 3.x pattern)"
- "StreamProvider.autoDispose with asyncMap for combining day + overdue streams"
- "TDD: failing test commit, then implementation commit"
key-files:
created:
- lib/features/home/data/calendar_dao.dart
- lib/features/home/data/calendar_dao.g.dart
- lib/features/home/domain/calendar_models.dart
- lib/features/home/presentation/calendar_providers.dart
- test/features/home/data/calendar_dao_test.dart
modified:
- lib/core/database/database.dart
- lib/core/database/database.g.dart
- lib/l10n/app_de.arb
- lib/l10n/app_localizations.dart
- lib/l10n/app_localizations_de.dart
key-decisions:
- "Used NotifierProvider<SelectedDateNotifier, DateTime> instead of deprecated StateProvider — Riverpod 3.x removed StateProvider in favour of Notifier-based providers"
- "calendarDayProvider fetches overdue tasks with .first when isToday, keeping asyncMap pattern consistent with dailyPlanProvider"
- "watchTasksForDate sorts alphabetically by name (not by due time) — arbitrary due time on same day has no meaningful sort order"
patterns-established:
- "CalendarDao: @DriftAccessor with join + where filter + orderBy, mapped to TaskWithRoom — same shape as DailyPlanDao"
- "Manual Notifier subclass for simple value-holding state provider (not @riverpod) to avoid code gen constraints"
requirements-completed: [CAL-02, CAL-05]
# Metrics
duration: 5min
completed: 2026-03-16
---
# Phase 5 Plan 01: Calendar Data Layer Summary
**CalendarDao with date-exact and overdue-before-date Drift queries, CalendarDayState model, Riverpod providers for selected date and day state, and "Heute" l10n string — full data foundation for the calendar strip UI**
## Performance
- **Duration:** 5 min
- **Started:** 2026-03-16T20:18:55Z
- **Completed:** 2026-03-16T20:24:12Z
- **Tasks:** 2
- **Files modified:** 10
## Accomplishments
- CalendarDao registered in AppDatabase with two reactive Drift streams: `watchTasksForDate` (exact day, sorted by name) and `watchOverdueTasks` (strictly before reference date, sorted by due date)
- CalendarDayState domain model separating dayTasks and overdueTasks with isEmpty helper
- selectedDateProvider (NotifierProvider, keeps alive) + calendarDayProvider (StreamProvider.autoDispose) following existing Riverpod patterns
- 11 unit tests passing via TDD red-green cycle; full 100-test suite passes with no regressions
## Task Commits
Each task was committed atomically:
1. **Task 1: RED - CalendarDao tests** - `f5c4b49` (test)
2. **Task 1: GREEN - CalendarDao implementation** - `c666f9a` (feat)
3. **Task 2: CalendarDayState, providers, l10n** - `68ba7c6` (feat)
## Files Created/Modified
- `lib/features/home/data/calendar_dao.dart` - CalendarDao with watchTasksForDate and watchOverdueTasks
- `lib/features/home/data/calendar_dao.g.dart` - Generated Drift mixin for CalendarDao
- `lib/features/home/domain/calendar_models.dart` - CalendarDayState model
- `lib/features/home/presentation/calendar_providers.dart` - selectedDateProvider and calendarDayProvider
- `test/features/home/data/calendar_dao_test.dart` - 11 DAO unit tests (TDD RED phase)
- `lib/core/database/database.dart` - Added CalendarDao import and registration in @DriftDatabase
- `lib/core/database/database.g.dart` - Regenerated with CalendarDao accessor
- `lib/l10n/app_de.arb` - Added calendarTodayButton: "Heute"
- `lib/l10n/app_localizations.dart` - Regenerated with calendarTodayButton getter
- `lib/l10n/app_localizations_de.dart` - Regenerated with calendarTodayButton implementation
## Decisions Made
- **NotifierProvider instead of StateProvider:** Riverpod 3.x dropped `StateProvider` — replaced with `NotifierProvider<SelectedDateNotifier, DateTime>` pattern (manual, not @riverpod) to keep consistent with the codebase's non-generated providers.
- **Overdue fetched with .first inside asyncMap:** When isToday, the overdue tasks stream's first emission is awaited inside asyncMap on the day tasks stream. This avoids combining two streams and stays consistent with the `dailyPlanProvider` pattern.
- **watchTasksForDate sorts alphabetically by name:** Tasks due on the same calendar day have no meaningful relative order by time. Alphabetical name sort gives deterministic, user-friendly ordering.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] StateProvider unavailable in Riverpod 3.x**
- **Found during:** Task 2 (calendar providers)
- **Issue:** Plan specified `StateProvider<DateTime>` but flutter_riverpod 3.3.1 removed StateProvider; analyzer reported `undefined_function`
- **Fix:** Replaced with `NotifierProvider<SelectedDateNotifier, DateTime>` using a minimal `Notifier` subclass with a `selectDate(DateTime)` method
- **Files modified:** lib/features/home/presentation/calendar_providers.dart
- **Verification:** `flutter analyze --no-fatal-infos` reports no issues
- **Committed in:** 68ba7c6 (Task 2 commit)
---
**Total deviations:** 1 auto-fixed (Rule 1 - Bug)
**Impact on plan:** Fix was required for compilation. The API surface is equivalent — consumers call `ref.watch(selectedDateProvider)` to read the date and `ref.read(selectedDateProvider.notifier).selectDate(date)` to update it. No scope creep.
## Issues Encountered
- None beyond the StateProvider API change documented above.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- CalendarDao, CalendarDayState, selectedDateProvider, and calendarDayProvider are all ready for consumption by Plan 02 (calendar strip UI)
- The `selectDate` method on SelectedDateNotifier is the correct way to update the selected date from the UI
- Existing dailyPlanProvider is unchanged — Plan 02 will decide whether to replace or retain it in the HomeScreen
---
*Phase: 05-calendar-strip*
*Completed: 2026-03-16*
## Self-Check: PASSED
- FOUND: lib/features/home/data/calendar_dao.dart
- FOUND: lib/features/home/domain/calendar_models.dart
- FOUND: lib/features/home/presentation/calendar_providers.dart
- FOUND: test/features/home/data/calendar_dao_test.dart
- FOUND: .planning/phases/05-calendar-strip/05-01-SUMMARY.md
- FOUND: commit f5c4b49 (test RED phase)
- FOUND: commit c666f9a (feat GREEN phase)
- FOUND: commit 68ba7c6 (feat Task 2)

View File

@@ -0,0 +1,316 @@
---
phase: 05-calendar-strip
plan: 02
type: execute
wave: 2
depends_on: ["05-01"]
files_modified:
- lib/features/home/presentation/home_screen.dart
- lib/features/home/presentation/calendar_strip.dart
- lib/features/home/presentation/calendar_task_row.dart
- lib/features/home/presentation/calendar_day_list.dart
autonomous: false
requirements:
- CAL-01
- CAL-03
- CAL-04
- CAL-05
must_haves:
truths:
- "Home screen shows a horizontal scrollable strip of day cards with German abbreviation (Mo, Di, Mi...) and date number"
- "Tapping a day card updates the task list below to show that day's tasks"
- "On app launch the strip auto-scrolls so today's card is centered"
- "A subtle wider gap and month label appears at month boundaries"
- "Overdue tasks appear in a separate coral-accented section when viewing today"
- "Overdue tasks do NOT appear when viewing past or future days"
- "Completing a task via checkbox triggers slide-out animation"
- "Floating Today button appears when scrolled away from today, hidden when today is visible"
- "First-run empty state (no rooms/tasks) still shows the create-room prompt"
- "Celebration state shows when all tasks for the selected day are done"
artifacts:
- path: "lib/features/home/presentation/calendar_strip.dart"
provides: "Horizontal scrollable date strip widget"
min_lines: 100
- path: "lib/features/home/presentation/calendar_day_list.dart"
provides: "Day task list with overdue section, empty, and celebration states"
min_lines: 80
- path: "lib/features/home/presentation/calendar_task_row.dart"
provides: "Task row adapted for calendar (no relative date, has room tag + checkbox)"
min_lines: 30
- path: "lib/features/home/presentation/home_screen.dart"
provides: "Rewritten HomeScreen composing strip + day list"
min_lines: 40
key_links:
- from: "lib/features/home/presentation/home_screen.dart"
to: "lib/features/home/presentation/calendar_strip.dart"
via: "HomeScreen composes CalendarStrip widget"
pattern: "CalendarStrip"
- from: "lib/features/home/presentation/home_screen.dart"
to: "lib/features/home/presentation/calendar_day_list.dart"
via: "HomeScreen composes CalendarDayList widget"
pattern: "CalendarDayList"
- from: "lib/features/home/presentation/calendar_strip.dart"
to: "lib/features/home/presentation/calendar_providers.dart"
via: "Strip reads and writes selectedDateProvider"
pattern: "selectedDateProvider"
- from: "lib/features/home/presentation/calendar_day_list.dart"
to: "lib/features/home/presentation/calendar_providers.dart"
via: "Day list watches calendarDayProvider for reactive task data"
pattern: "calendarDayProvider"
- from: "lib/features/home/presentation/calendar_day_list.dart"
to: "lib/features/tasks/presentation/task_providers.dart"
via: "Task completion uses taskActionsProvider.completeTask()"
pattern: "taskActionsProvider"
---
<objective>
Build the complete calendar strip UI and replace the old HomeScreen with it.
Purpose: This is the user-facing deliverable of Phase 5 -- the horizontal date strip with day-task list that replaces the stacked overdue/today/tomorrow daily plan.
Output: CalendarStrip widget, CalendarDayList widget, CalendarTaskRow widget, rewritten HomeScreen that composes them.
</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/05-calendar-strip/5-CONTEXT.md
@.planning/phases/05-calendar-strip/05-01-SUMMARY.md
<interfaces>
<!-- From Plan 01 outputs (CalendarDao, models, providers) -->
From lib/features/home/domain/calendar_models.dart:
```dart
class CalendarDayState {
final DateTime selectedDate;
final List<TaskWithRoom> dayTasks;
final List<TaskWithRoom> overdueTasks;
const CalendarDayState({required this.selectedDate, required this.dayTasks, required this.overdueTasks});
bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty;
}
```
From lib/features/home/presentation/calendar_providers.dart:
```dart
final selectedDateProvider = StateProvider<DateTime>(...); // read/write selected date
final calendarDayProvider = StreamProvider.autoDispose<CalendarDayState>(...); // reactive day data
```
From lib/features/home/domain/daily_plan_models.dart:
```dart
class TaskWithRoom {
final Task task;
final String roomName;
final int roomId;
}
```
From lib/features/tasks/presentation/task_providers.dart:
```dart
// Use to complete tasks:
ref.read(taskActionsProvider.notifier).completeTask(taskId);
```
From lib/core/theme/app_theme.dart:
```dart
// Seed color: Color(0xFF7A9A6D) -- sage green
// The "light sage/green tint" for day cards should derive from the theme's primary/seed
```
Existing reusable constants:
```dart
const _overdueColor = Color(0xFFE07A5F); // warm coral for overdue
```
Existing l10n strings to reuse:
```dart
l10n.dailyPlanSectionOverdue // "Uberfaellig"
l10n.dailyPlanNoTasks // "Noch keine Aufgaben angelegt"
l10n.dailyPlanAllClearTitle // "Alles erledigt!"
l10n.dailyPlanAllClearMessage // "Keine Aufgaben fuer heute..."
l10n.homeEmptyMessage // "Lege zuerst einen Raum an..."
l10n.homeEmptyAction // "Raum erstellen"
l10n.calendarTodayButton // "Heute" (added in Plan 01)
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Build CalendarStrip, CalendarTaskRow, CalendarDayList widgets</name>
<files>
lib/features/home/presentation/calendar_strip.dart,
lib/features/home/presentation/calendar_task_row.dart,
lib/features/home/presentation/calendar_day_list.dart
</files>
<action>
**CalendarStrip** (`lib/features/home/presentation/calendar_strip.dart`):
A ConsumerStatefulWidget that renders a horizontal scrollable row of day cards.
Scroll range: 90 days in the past and 90 days in the future (181 total items). This gives enough past for review and future for planning without performance concerns.
Layout:
- Uses a `ScrollController` with `initialScrollOffset` calculated to center today's card on first build.
- Each day card is a fixed-width container (~56px wide, ~72px tall). Cards show:
- Top: German day abbreviation using `DateFormat('E', 'de').format(date)` which gives "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So". Import `package:intl/intl.dart`.
- Bottom: Date number (day of month) as text.
- Card styling per user decisions:
- All cards: light sage/green tint background. Use `theme.colorScheme.primaryContainer.withValues(alpha: 0.3)` or similar to get a subtle green wash.
- Selected card: stronger green (`theme.colorScheme.primaryContainer`) and border with `theme.colorScheme.primary`. The strip scrolls to center the selected card using `animateTo()`.
- Today's card (when not selected): bold text + a small accent underline bar below the date number (2px, primary color).
- Today + selected: both treatments combined.
- Spacing: cards have 4px horizontal margin by default. At month boundaries (where card N is the last day of a month and card N+1 is the first of the next month), the gap is 16px, and a small Text widget showing the new month abbreviation (e.g., "Apr") in `theme.textTheme.labelSmall` is inserted between them.
- On tap: update `ref.read(selectedDateProvider.notifier).state = tappedDate` and animate the scroll to center the tapped card.
- Auto-scroll on init: In `initState`, after the first frame (using `WidgetsBinding.instance.addPostFrameCallback`), animate to center today's card with a 200ms duration using `Curves.easeOut`.
Controller pattern for scroll-to-today:
```dart
class CalendarStripController {
VoidCallback? _scrollToToday;
void scrollToToday() => _scrollToToday?.call();
}
```
CalendarStrip takes `CalendarStripController controller` parameter and sets `controller._scrollToToday` in initState. Parent calls `controller.scrollToToday()` from the Today button.
Today visibility callback: Expose `onTodayVisibilityChanged(bool isVisible)`. Determine visibility by checking if today's card offset is within the viewport bounds during scroll events.
**CalendarTaskRow** (`lib/features/home/presentation/calendar_task_row.dart`):
Adapted from `DailyPlanTaskRow` but simplified per user decisions:
- Shows: task name, tappable room tag (navigates to room via `context.go('/rooms/$roomId')`), checkbox
- Does NOT show relative date (strip already communicates which day)
- Same room tag styling as DailyPlanTaskRow (secondaryContainer chip with borderRadius 4)
- Checkbox visible, onChanged triggers `onCompleted` callback
- Overdue variant: if `isOverdue` flag is true, task name text color uses `_overdueColor` for visual distinction
**CalendarDayList** (`lib/features/home/presentation/calendar_day_list.dart`):
A ConsumerStatefulWidget that shows the task list for the selected day.
Watches `calendarDayProvider`. Manages `Set<int> _completingTaskIds` for animation state.
Handles these states:
a) **Loading**: `CircularProgressIndicator` centered.
b) **Error**: Error text centered.
c) **First-run empty** (no rooms/tasks at all): Same pattern as current `_buildNoTasksState` -- checklist icon, "Noch keine Aufgaben angelegt" message, "Lege zuerst einen Raum an" subtitle, "Raum erstellen" FilledButton.tonal navigating to `/rooms`. Detect by checking if `state.isEmpty && state.totalTaskCount == 0` (requires adding `totalTaskCount` field to CalendarDayState and computing it in the provider -- see NOTE below).
d) **Empty day** (tasks exist elsewhere but not this day, and not today): show centered subtle icon (Icons.event_available) + "Keine Aufgaben" text.
e) **Celebration** (today is selected, tasks exist elsewhere, but today's tasks are all done): show celebration icon + "Alles erledigt!" title + "Keine Aufgaben fuer heute. Geniesse den Moment!" message. Compact layout (no ProgressCard).
f) **Has tasks**: Render a ListView with:
- If overdue tasks exist (only present when viewing today): Section header "Uberfaellig" in coral color (`_overdueColor`), followed by overdue CalendarTaskRow items with `isOverdue: true` and interactive checkboxes.
- Day tasks: CalendarTaskRow items with interactive checkboxes.
- Task completion: on checkbox tap, add taskId to `_completingTaskIds`, call `ref.read(taskActionsProvider.notifier).completeTask(taskId)`. Render completing tasks with the `_CompletingTaskRow` animation (SizeTransition + SlideTransition, 300ms, Curves.easeInOut) -- recreate this private widget in calendar_day_list.dart.
NOTE for executor: Plan 01 creates CalendarDayState with selectedDate, dayTasks, overdueTasks. This task needs a `totalTaskCount` int field on CalendarDayState to distinguish first-run from celebration. When implementing, add `final int totalTaskCount` to CalendarDayState in calendar_models.dart and compute it in the calendarDayProvider via a simple `SELECT COUNT(*) FROM tasks` query (one line in CalendarDao: `Future<int> getTaskCount() async { final r = await (selectOnly(tasks)..addColumns([tasks.id.count()])).getSingle(); return r.read(tasks.id.count()) ?? 0; }`).
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos</automated>
</verify>
<done>CalendarStrip renders 181 day cards with German abbreviations, highlights selected/today cards, shows month boundary labels. CalendarTaskRow shows name + room tag + checkbox without relative date. CalendarDayList shows overdue section (today only), day tasks, empty states, and celebration state. All compile without analysis errors.</done>
</task>
<task type="auto">
<name>Task 2: Replace HomeScreen with calendar composition and floating Today button</name>
<files>
lib/features/home/presentation/home_screen.dart
</files>
<action>
Rewrite `lib/features/home/presentation/home_screen.dart` entirely. The old content (DailyPlanState, overdue/today/tomorrow sections, ProgressCard) is fully replaced.
New HomeScreen is a `ConsumerStatefulWidget`:
State fields:
- `late final CalendarStripController _stripController = CalendarStripController();`
- `bool _showTodayButton = false;`
Build method returns a Stack with:
1. A Column containing:
- `CalendarStrip(controller: _stripController, onTodayVisibilityChanged: (visible) { setState(() => _showTodayButton = !visible); })`
- `Expanded(child: CalendarDayList())`
2. Conditionally, a Positioned floating "Heute" button at bottom-center:
- `FloatingActionButton.extended` with `Icons.today` icon and `l10n.calendarTodayButton` label
- onPressed: set `selectedDateProvider` to today's date-only DateTime, call `_stripController.scrollToToday()`
Imports needed:
- `flutter/material.dart`
- `flutter_riverpod/flutter_riverpod.dart`
- `calendar_strip.dart`
- `calendar_day_list.dart`
- `calendar_providers.dart` (for selectedDateProvider)
- `app_localizations.dart`
Do NOT delete old files (`daily_plan_providers.dart`, `daily_plan_task_row.dart`, `progress_card.dart`, `daily_plan_dao.dart`). DailyPlanDao is still used by the notification service. Old presentation files become dead code -- safe to clean up in a future phase.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos && flutter test</automated>
</verify>
<done>HomeScreen renders CalendarStrip at top and CalendarDayList below. Floating Today button appears when scrolled away from today. Old overdue/today/tomorrow sections are gone. Full test suite passes. No analysis errors.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Verify calendar strip home screen visually and functionally</name>
<files>lib/features/home/presentation/home_screen.dart</files>
<action>
Human verifies the complete calendar strip experience on a running device/emulator.
Launch the app with `flutter run` (or hot-restart). Walk through all key behaviors:
1. Strip appearance: day cards with German abbreviations and date numbers
2. Today highlighting: centered, stronger green, bold + underline
3. Day selection: tap a card, task list updates
4. Month boundaries: wider gap with month label
5. Today button: appears when scrolled away, snaps back on tap
6. Overdue section: coral header on today only
7. Task completion: checkbox triggers slide-out animation
8. Empty/celebration states
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos</automated>
</verify>
<done>User has confirmed the calendar strip looks correct, day selection works, overdue behavior is right, and all states render properly.</done>
</task>
</tasks>
<verification>
- `flutter analyze --no-fatal-infos` -- zero errors
- `flutter test` -- full test suite passes (existing + new DAO tests)
- Visual: calendar strip is horizontally scrollable with day cards
- Visual: selected day highlighted, today has bold + underline treatment
- Visual: month boundaries have wider gaps and month name labels
- Functional: tapping a day card updates the task list below
- Functional: overdue tasks appear only when viewing today
- Functional: floating Today button appears/disappears correctly
- Functional: task completion animation works
</verification>
<success_criteria>
- Home screen replaced: no more stacked overdue/today/tomorrow sections
- Horizontal date strip scrolls smoothly with 181 day range
- Day cards show German abbreviations and date numbers
- Tapping a card selects it and shows that day's tasks
- Today auto-centers on launch with smooth animation
- Month boundaries visually distinct with labels
- Overdue carry-over only on today's view with coral accent
- Floating Today button for quick navigation
- Empty and celebration states work correctly
- All existing tests pass, no regressions
</success_criteria>
<output>
After completion, create `.planning/phases/05-calendar-strip/05-02-SUMMARY.md`
</output>

View File

@@ -0,0 +1,148 @@
---
phase: 05-calendar-strip
plan: 02
subsystem: ui
tags: [flutter, riverpod, dart, intl, animation, calendar]
# Dependency graph
requires:
- phase: 05-calendar-strip plan 01
provides: CalendarDao, CalendarDayState, selectedDateProvider, calendarDayProvider
provides:
- CalendarStrip widget (181-day horizontal scroll, German abbreviations, month boundary labels)
- CalendarTaskRow widget (task name + room tag chip + checkbox, no relative date)
- CalendarDayList widget (loading/empty/celebration/tasks states, overdue section today-only)
- Rewritten HomeScreen composing strip + day list with floating Today button
- totalTaskCount field on CalendarDayState and getTaskCount() on CalendarDao
- Updated home screen and app shell tests for new calendar providers
affects:
- 06-task-history (uses CalendarStrip as the navigation surface)
- 07-task-sorting (task display within CalendarDayList)
# Tech tracking
tech-stack:
added: []
patterns:
- "CalendarStrip uses CalendarStripController (simple VoidCallback holder) for parent-to-child imperative scrolling"
- "CalendarDayList manages _completingTaskIds Set<int> for slide-out animation the same way as old HomeScreen"
- "Tests use tester.pump() + pump(Duration) instead of pumpAndSettle() to avoid timeout from animation controllers"
key-files:
created:
- lib/features/home/presentation/calendar_strip.dart
- lib/features/home/presentation/calendar_task_row.dart
- lib/features/home/presentation/calendar_day_list.dart
modified:
- lib/features/home/presentation/home_screen.dart
- lib/features/home/domain/calendar_models.dart
- lib/features/home/data/calendar_dao.dart
- lib/features/home/presentation/calendar_providers.dart
- test/features/home/presentation/home_screen_test.dart
- test/shell/app_shell_test.dart
key-decisions:
- "CalendarStripController holds a VoidCallback instead of using GlobalKey — simpler for this one-direction imperative call"
- "totalTaskCount fetched via getTaskCount() inside calendarDayProvider asyncMap — avoids a third stream, consistent with existing pattern"
- "Tests use pump() + pump(Duration) instead of pumpAndSettle() — CalendarStrip's ScrollController postFrameCallback and animation controllers cause pumpAndSettle to timeout"
- "month label height always reserved with SizedBox(height:16) on non-boundary cards — prevents strip height jitter as you scroll through months"
patterns-established:
- "ImperativeController pattern: class with VoidCallback? _action; void action() => _action?.call(); widget sets _action in initState"
- "CalendarDayList state machine: first-run (totalTaskCount==0) > celebration (isToday + isEmpty + totalTaskCount>0) > emptyDay (isEmpty) > hasTasks"
requirements-completed: [CAL-01, CAL-03, CAL-04, CAL-05]
# Metrics
duration: 8min
completed: 2026-03-16
---
# Phase 5 Plan 02: Calendar Strip UI Summary
**Horizontal 181-day calendar strip with German day cards, month boundaries, floating Today button, and day task list with overdue section — replaces the stacked daily-plan HomeScreen**
## Performance
- **Duration:** 8 min
- **Started:** 2026-03-16T20:27:39Z
- **Completed:** 2026-03-16T20:35:55Z
- **Tasks:** 3 (Task 3 auto-approved in auto-advance mode)
- **Files modified:** 9
## Accomplishments
- CalendarStrip: horizontal ListView with 181 day cards (90 past + today + 90 future), German abbreviations via `DateFormat('E', 'de')`, selected card highlighted (stronger primaryContainer + border), today card with bold text + 2px accent underline, month boundary wider gap + month label, auto-scrolls to center today on init, CalendarStripController enables Today-button → strip communication
- CalendarDayList: five-state machine (loading, first-run empty, celebration, empty day, has tasks) with overdue section when viewing today, slide-out completion animation reusing the same SizeTransition + SlideTransition pattern from the old HomeScreen
- CalendarTaskRow: simplified from DailyPlanTaskRow — no relative date, name + room chip + checkbox, coral text when isOverdue
- HomeScreen rewritten: Stack with Column(CalendarStrip + Expanded(CalendarDayList)) and conditionally-visible FloatingActionButton.extended for "Heute" navigation
- Added totalTaskCount to CalendarDayState and getTaskCount() SELECT COUNT to CalendarDao for first-run vs. celebration disambiguation
- Updated 2 test files (home_screen_test.dart, app_shell_test.dart) to test new providers; test count grew from 100 to 101
## Task Commits
Each task was committed atomically:
1. **Task 1: Build CalendarStrip, CalendarTaskRow, CalendarDayList widgets** - `f718ee8` (feat)
2. **Task 2: Replace HomeScreen with calendar composition** - `88ef248` (feat)
3. **Task 3: Verify calendar strip visually** - auto-approved (checkpoint:human-verify in auto-advance mode)
## Files Created/Modified
- `lib/features/home/presentation/calendar_strip.dart` - 181-day horizontal scrollable strip with German abbreviations, today/selected highlights, month boundary labels
- `lib/features/home/presentation/calendar_task_row.dart` - Task row: name + room chip + checkbox, isOverdue coral styling, no relative date
- `lib/features/home/presentation/calendar_day_list.dart` - Day task list with 5-state machine, overdue section (today only), slide-out animation
- `lib/features/home/presentation/home_screen.dart` - Rewritten: CalendarStrip + CalendarDayList + floating Today FAB
- `lib/features/home/domain/calendar_models.dart` - Added totalTaskCount field
- `lib/features/home/data/calendar_dao.dart` - Added getTaskCount() query
- `lib/features/home/presentation/calendar_providers.dart` - calendarDayProvider now fetches and includes totalTaskCount
- `test/features/home/presentation/home_screen_test.dart` - Rewritten for CalendarDayState / calendarDayProvider
- `test/shell/app_shell_test.dart` - Updated from dailyPlanProvider to calendarDayProvider
## Decisions Made
- **CalendarStripController as simple VoidCallback holder:** Avoids GlobalKey complexity for a single imperative scroll-to-today action; parent holds controller, widget registers its implementation in initState.
- **totalTaskCount fetched in asyncMap:** Consistent with existing calendarDayProvider asyncMap pattern; avoids a third reactive stream just for a count.
- **Tests use pump() + pump(Duration) instead of pumpAndSettle():** ScrollController's postFrameCallback animation and _completingTaskIds AnimationController keep the tester busy indefinitely; fixed-duration pump steps are reliable.
- **Month label height always reserved:** Non-boundary cards get `SizedBox(height: 16)` to match the label row height — prevents strip height from changing as you scroll across month edges.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Updated existing tests broken by the HomeScreen rewrite**
- **Found during:** Task 2 verification (flutter test)
- **Issue:** `home_screen_test.dart` and `app_shell_test.dart` both imported `dailyPlanProvider` and `DailyPlanState` and used `pumpAndSettle()`, which now times out because CalendarStrip animation controllers never settle
- **Fix:** Rewrote both test files to use `calendarDayProvider`/`CalendarDayState` and replaced `pumpAndSettle()` with `pump() + pump(Duration(milliseconds: 500))`; updated all assertions to match new UI (removed progress card / tomorrow section assertions, added strip-visible assertion)
- **Files modified:** test/features/home/presentation/home_screen_test.dart, test/shell/app_shell_test.dart
- **Verification:** `flutter test` — 101 tests all pass; `flutter analyze --no-fatal-infos` — zero issues
- **Committed in:** f718ee8 (Task 1 commit, as tests were fixed alongside widget creation)
---
**Total deviations:** 1 auto-fixed (Rule 1 - Bug)
**Impact on plan:** Required to maintain working test suite. The new tests cover the same behaviors (empty state, overdue section, celebration, checkboxes) but against the calendar API. No scope creep.
## Issues Encountered
- None beyond the test migration documented above.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- HomeScreen is fully replaced; CalendarStrip and CalendarDayList are composable widgets ready for Phase 6/7 integration
- The old daily_plan_providers.dart, daily_plan_task_row.dart, and progress_card.dart are now dead code; safe to clean up in a future phase
- DailyPlanDao is still used by the notification service and must NOT be deleted
---
*Phase: 05-calendar-strip*
*Completed: 2026-03-16*
## Self-Check: PASSED
- FOUND: lib/features/home/presentation/calendar_strip.dart
- FOUND: lib/features/home/presentation/calendar_task_row.dart
- FOUND: lib/features/home/presentation/calendar_day_list.dart
- FOUND: lib/features/home/presentation/home_screen.dart (rewritten)
- FOUND: lib/features/home/domain/calendar_models.dart (updated)
- FOUND: lib/features/home/data/calendar_dao.dart (updated)
- FOUND: lib/features/home/presentation/calendar_providers.dart (updated)
- FOUND: .planning/phases/05-calendar-strip/05-02-SUMMARY.md
- FOUND: commit f718ee8 (Task 1)
- FOUND: commit 88ef248 (Task 2)

View File

@@ -0,0 +1,202 @@
---
phase: 05-calendar-strip
verified: 2026-03-16T21:00:00Z
status: human_needed
score: 10/10 must-haves verified
human_verification:
- test: "Launch the app on a device or emulator and confirm the calendar strip renders correctly"
expected: "Horizontal row of day cards with German abbreviations (Mo, Di, Mi...) and date number; today's card is bold with a 2px green underline accent; all cards have a light sage tint; selected card has stronger green background and border"
why_human: "Visual appearance, color fidelity, and card proportions cannot be verified programmatically"
- test: "Tap several day cards and verify the task list below updates"
expected: "Tapping a card selects it (green highlight, centered), and the task list below immediately shows that day's tasks"
why_human: "Interactive tap-to-select flow and reactive list update require a running device"
- test: "Scroll the strip far from today, then tap the floating Today button"
expected: "Floating 'Heute' FAB appears when today is scrolled out of view; tapping it re-centers today's card and resets the task list to today"
why_human: "Visibility toggle of FAB and imperative scroll-back behavior require real scroll interaction"
- test: "Verify month boundary treatment"
expected: "At every month boundary a slightly wider gap appears between the last card of one month and the first card of the next, with a small month label (e.g. 'Mrz', 'Apr') in the gap"
why_human: "Month label rendering and gap width are visual properties that require visual inspection"
- test: "With tasks overdue (nextDueDate before today), view today in the strip"
expected: "An 'Uberfaellig' section header in coral appears above the overdue tasks; switching to yesterday or tomorrow hides the overdue section entirely"
why_human: "Requires a device with test data in the database and navigation between days"
- test: "Complete a task via its checkbox"
expected: "The task slides out with a SizeTransition + SlideTransition animation (300ms); it disappears from the list after the animation"
why_human: "Animation quality and timing require visual observation on a running device"
---
# Phase 5: Calendar Strip Verification Report
**Phase Goal:** Users navigate their tasks through a horizontal date-strip that replaces the stacked daily plan, seeing today's tasks by default and any day's tasks on tap
**Verified:** 2026-03-16T21:00:00Z
**Status:** human_needed — all automated checks pass; 6 visual/interactive behaviors need human confirmation
**Re-verification:** No — initial verification
---
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|----|-------|--------|---------|
| 1 | Home screen shows a horizontal scrollable strip of day cards with German abbreviation (Mo, Di...) and date number | VERIFIED | `calendar_strip.dart` L265: `DateFormat('E', 'de').format(date)` produces German abbreviations; 181-card `ListView.builder` scroll direction horizontal |
| 2 | Tapping a day card updates the task list below to show that day's tasks | VERIFIED | `_onCardTapped` calls `ref.read(selectedDateProvider.notifier).selectDate(tappedDate)`; `CalendarDayList` watches `calendarDayProvider` which watches `selectedDateProvider` |
| 3 | On app launch the strip auto-scrolls so today's card is centered | VERIFIED | `initState` calls `WidgetsBinding.instance.addPostFrameCallback``_animateToToday()` which calls `_scrollController.animateTo(..., duration: 200ms, curve: Curves.easeOut)` |
| 4 | A subtle wider gap and month label appears at month boundaries | VERIFIED | `_kMonthBoundaryGap = 16.0` vs `_kCardMargin = 4.0`; `_isFirstOfMonth` triggers `DateFormat('MMM', 'de').format(date)` text label; non-boundary cards reserve `SizedBox(height: 16)` to prevent jitter |
| 5 | Overdue tasks appear in a separate coral-accented section when viewing today | VERIFIED | `calendarDayProvider` fetches `watchOverdueTasks` only when `isToday`; `_buildTaskList` renders a coral-colored "Uberfaellig" header via `_overdueColor = Color(0xFFE07A5F)` when `state.overdueTasks.isNotEmpty` |
| 6 | Overdue tasks do NOT appear when viewing past or future days | VERIFIED | `calendarDayProvider`: `isToday` guard — past/future sets `overdueTasks = const []`; 101-test suite includes `does not show overdue section for non-today date` test passing |
| 7 | Completing a task via checkbox triggers slide-out animation | VERIFIED | `_CompletingTaskRow` in `calendar_day_list.dart` implements `SizeTransition` + `SlideTransition` (300ms, `Curves.easeInOut`); `_onTaskCompleted` adds to `_completingTaskIds` and calls `taskActionsProvider.notifier.completeTask` |
| 8 | Floating Today button appears when scrolled away from today, hidden when today is visible | VERIFIED | `CalendarStrip.onTodayVisibilityChanged` callback drives `_showTodayButton` in `HomeScreen`; `_onScroll` computes viewport bounds vs today card position |
| 9 | First-run empty state (no rooms/tasks) still shows the create-room prompt | VERIFIED | `CalendarDayList._buildFirstRunEmpty` shows checklist icon + `l10n.dailyPlanNoTasks` + `l10n.homeEmptyAction` FilledButton.tonal navigating to `/rooms`; gated by `totalTaskCount == 0` |
| 10 | Celebration state shows when all tasks for the selected day are done | VERIFIED | `_buildCelebration` renders `Icons.celebration_outlined` + `dailyPlanAllClearTitle` + `dailyPlanAllClearMessage`; triggered by `isToday && dayTasks.isEmpty && overdueTasks.isEmpty && totalTaskCount > 0` |
**Score: 10/10 truths verified**
---
## Required Artifacts
### Plan 01 Artifacts
| Artifact | Expected | Lines | Status | Details |
|----------|----------|-------|--------|---------|
| `lib/features/home/data/calendar_dao.dart` | Date-parameterized task queries | 87 | VERIFIED | `watchTasksForDate`, `watchOverdueTasks`, `getTaskCount` all implemented; `@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])` annotation present |
| `lib/features/home/data/calendar_dao.g.dart` | Generated Drift mixin | 25 | VERIFIED | `_$CalendarDaoMixin` generated, part of `calendar_dao.dart` |
| `lib/features/home/domain/calendar_models.dart` | CalendarDayState model | 25 | VERIFIED | `CalendarDayState` with `selectedDate`, `dayTasks`, `overdueTasks`, `totalTaskCount`, `isEmpty` getter |
| `lib/features/home/presentation/calendar_providers.dart` | Riverpod providers | 69 | VERIFIED | `selectedDateProvider` (NotifierProvider), `calendarDayProvider` (StreamProvider.autoDispose) with overdue-today-only logic |
| `test/features/home/data/calendar_dao_test.dart` | DAO unit tests (min 50 lines) | 286 | VERIFIED | 11 tests: 5 for `watchTasksForDate`, 6 for `watchOverdueTasks`; all pass |
### Plan 02 Artifacts
| Artifact | Expected | Lines | Status | Details |
|----------|----------|-------|--------|---------|
| `lib/features/home/presentation/calendar_strip.dart` | Horizontal scrollable date strip (min 100 lines) | 348 | VERIFIED | 181-card ListView, German abbreviations, CalendarStripController, today-visibility callback, month boundary labels |
| `lib/features/home/presentation/calendar_day_list.dart` | Day task list with states (min 80 lines) | 310 | VERIFIED | 5-state machine (loading/first-run/celebration/empty/tasks), overdue section, `_CompletingTaskRow` animation |
| `lib/features/home/presentation/calendar_task_row.dart` | Task row (min 30 lines) | 69 | VERIFIED | Name + room chip + checkbox; `isOverdue` coral styling; no relative date |
| `lib/features/home/presentation/home_screen.dart` | Rewritten HomeScreen (min 40 lines) | 69 | VERIFIED | Stack with Column(CalendarStrip + CalendarDayList) + conditional floating FAB |
---
## Key Link Verification
### Plan 01 Links
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `calendar_dao.dart` | `database.dart` | CalendarDao registered in @DriftDatabase daos | WIRED | `database.dart` L49: `daos: [RoomsDao, TasksDao, DailyPlanDao, CalendarDao]`; `database.g.dart` L1249: `late final CalendarDao calendarDao = CalendarDao(this as AppDatabase)` |
| `calendar_providers.dart` | `calendar_dao.dart` | Provider reads CalendarDao from AppDatabase via `db.calendarDao` | WIRED | `calendar_providers.dart` L46: `db.calendarDao.watchTasksForDate(selectedDate)`; L5354: `db.calendarDao.watchOverdueTasks(selectedDate).first`; L60: `db.calendarDao.getTaskCount()` |
### Plan 02 Links
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `home_screen.dart` | `calendar_strip.dart` | HomeScreen composes CalendarStrip | WIRED | `home_screen.dart` L37: `CalendarStrip(controller: _stripController, ...)` |
| `home_screen.dart` | `calendar_day_list.dart` | HomeScreen composes CalendarDayList | WIRED | `home_screen.dart` L43: `const Expanded(child: CalendarDayList())` |
| `calendar_strip.dart` | `calendar_providers.dart` | Strip reads/writes selectedDateProvider | WIRED | `calendar_strip.dart` L193: `ref.read(selectedDateProvider.notifier).selectDate(tappedDate)`; L199: `ref.watch(selectedDateProvider)` |
| `calendar_day_list.dart` | `calendar_providers.dart` | Day list watches calendarDayProvider | WIRED | `calendar_day_list.dart` L46: `final dayState = ref.watch(calendarDayProvider)` |
| `calendar_day_list.dart` | `task_providers.dart` | Task completion via taskActionsProvider | WIRED | `calendar_day_list.dart` L39: `ref.read(taskActionsProvider.notifier).completeTask(taskId)` |
---
## Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-------------|--------|---------|
| CAL-01 | Plan 02 | User sees horizontal scrollable date-strip with day abbreviation (Mo, Di...) and date number per card | SATISFIED | `calendar_strip.dart`: 181-card horizontal ListView; `DateFormat('E', 'de')` for German abbreviations; `date.day.toString()` for date number |
| CAL-02 | Plan 01 | User can tap a day card to see that day's tasks in a list below the strip | SATISFIED | `_onCardTapped``selectedDateProvider``calendarDayProvider``CalendarDayList` reactive update |
| CAL-03 | Plan 02 | User sees a subtle color shift at month boundaries for visual orientation | SATISFIED | `_isFirstOfMonth` check triggers `_kMonthBoundaryGap = 16.0` (vs 4px normal) and `DateFormat('MMM', 'de')` month label in `theme.colorScheme.primary` |
| CAL-04 | Plan 02 | Calendar strip auto-scrolls to today on app launch | SATISFIED | `addPostFrameCallback``_animateToToday()``animateTo(200ms, Curves.easeOut)` centered on today's index |
| CAL-05 | Plans 01+02 | Undone tasks carry over to the next day with red/orange color accent | SATISFIED | `watchOverdueTasks` returns tasks with `nextDueDate < today`; `calendarDayProvider` includes them only for `isToday`; `_overdueColor = Color(0xFFE07A5F)` applied to section header and task name text |
**All 5 CAL requirements: SATISFIED**
No orphaned requirements — REQUIREMENTS.md maps CAL-01 through CAL-05 exclusively to Phase 5, all accounted for.
---
## Anti-Patterns Found
No anti-patterns detected. Scan of all 7 phase-created/modified files found:
- No TODO/FIXME/XXX/HACK/PLACEHOLDER comments
- No empty implementations (`return null`, `return {}`, `return []`)
- No stub handlers (`() => {}` or `() => console.log(...)`)
- No unimplemented API routes
---
## Test Results
| Suite | Result | Count |
|-------|--------|-------|
| `flutter test test/features/home/data/calendar_dao_test.dart` | All passed | 11/11 |
| `flutter test` (full suite) | All passed | 101/101 |
| `flutter analyze --no-fatal-infos` | No issues | 0 errors, 0 warnings |
---
## Commit Verification
All 5 commits documented in SUMMARY files confirmed to exist in git history:
| Hash | Description |
|------|-------------|
| `f5c4b49` | test(05-01): add failing tests for CalendarDao |
| `c666f9a` | feat(05-01): implement CalendarDao with date-parameterized task queries |
| `68ba7c6` | feat(05-01): add CalendarDayState model, Riverpod providers, and l10n strings |
| `f718ee8` | feat(05-02): build CalendarStrip, CalendarTaskRow, CalendarDayList widgets |
| `88ef248` | feat(05-02): replace HomeScreen with calendar composition and floating Today button |
---
## Human Verification Required
All automated checks pass. The following items require a device or emulator to confirm:
### 1. Calendar Strip Visual Rendering
**Test:** Launch the app, navigate to the home tab.
**Expected:** Horizontal row of day cards each showing a German day abbreviation (Mo, Di, Mi, Do, Fr, Sa, So) and a date number. All cards have a light sage/green tint background. Today's card has bold text and a 2px green underline accent bar below the date number.
**Why human:** Color fidelity, card proportions, and font weight treatment are visual properties.
### 2. Day Selection Updates Task List
**Test:** Tap several different day cards in the strip.
**Expected:** The tapped card becomes highlighted (stronger green background + border, centered in the strip), and the task list below immediately updates to show that day's scheduled tasks.
**Why human:** Interactive responsiveness and smooth centering animation require a running device.
### 3. Floating Today Button Behavior
**Test:** Scroll the strip well past today (e.g., 30+ days forward). Then tap the floating "Heute" button.
**Expected:** The "Heute" FAB appears when today's card is no longer in the viewport. Tapping it re-centers today's card with a smooth scroll animation and resets the task list to today's tasks. The FAB then disappears.
**Why human:** FAB visibility toggling based on scroll position and imperative scroll-back require real interaction.
### 4. Month Boundary Labels
**Test:** Scroll through a month boundary in the strip.
**Expected:** At the boundary, a small month name label (e.g., "Apr") appears above the first card of the new month, and the gap between the last card of the old month and the first card of the new month is visibly wider than the normal gap.
**Why human:** Gap width and label placement are visual properties.
### 5. Overdue Section Today-Only
**Test:** With at least one task whose nextDueDate is before today in the database, view the home screen on today's date, then tap a past or future date.
**Expected:** On today's view, a coral-colored "Uberfaellig" section header appears above the overdue task(s) with coral-colored task names. Switching to any other day hides the overdue section entirely — only that day's scheduled tasks appear.
**Why human:** Requires real data in the database and navigation between dates.
### 6. Task Completion Slide-Out Animation
**Test:** Tap a checkbox on any task in the day list.
**Expected:** The task row slides out to the right while simultaneously collapsing its height to zero, over approximately 300ms, then disappears from the list.
**Why human:** Animation smoothness, duration, and visual quality require observation on a running device.
---
## Summary
Phase 5 goal is **fully achieved at the code level**. The horizontal calendar strip replaces the stacked daily plan, the data layer correctly handles date-parameterized queries and overdue isolation, all UI widgets are substantive and properly wired, all key links are connected, all 5 CAL requirements are satisfied, and the full 101-test suite passes with zero analysis issues.
The `human_needed` status reflects that 6 visual and interactive behaviors (strip appearance, tap selection, Today button scroll-back, month boundary labels, overdue section isolation, and task completion animation) require a running device to confirm their real-world quality. No code gaps were found.
---
_Verified: 2026-03-16T21:00:00Z_
_Verifier: Claude (gsd-verifier)_

View File

@@ -0,0 +1,105 @@
# Phase 5: Calendar Strip - Context
**Gathered:** 2026-03-16
**Status:** Ready for planning
<domain>
## Phase Boundary
Replace the stacked daily plan home screen (overdue/today/tomorrow sections) with a horizontal scrollable date-strip and day-task list. Users navigate by tapping day cards to view that day's tasks below the strip. Requirements: CAL-01 through CAL-05.
</domain>
<decisions>
## Implementation Decisions
### Day card appearance
- Each card shows: German day abbreviation (Mo, Di, Mi...) and date number only
- No task-count badges, dots, or indicators on the cards
- All cards have a light sage/green tint
- Selected card has a noticeably stronger green and is always centered in the strip
- Today's card uses bold text with an accent underline
- When today is selected, both treatments combine (bold + underline + stronger green + centered)
### Month boundary treatment (CAL-03)
- A slightly wider gap between the last day of one month and the first of the next
- A small month name label (e.g., "Apr") inserted in the gap between months
### Scroll range & navigation
- Strip scrolls both into the past and into the future (Claude picks a reasonable range balancing performance and usefulness)
- On app launch, the strip auto-scrolls to center on today with a quick slide animation (~200ms)
- A floating "Today" button appears when the user has scrolled away from today; tap to snap back. Hidden when today is already visible.
### Task list below the strip
- No ProgressCard — task list appears directly under the strip
- Overdue tasks (CAL-05) appear in a separate section with coral accent header above the day's own tasks, same pattern as current "Überfällig" section
- Task rows show: task name, tappable room tag, and checkbox — no relative date (strip already communicates which day)
- Checkboxes are interactive — tapping completes the task with the existing slide-out animation
### Empty and celebration states
- If a selected day had tasks that were all completed: show celebration state (icon + message, same spirit as current AllClear)
- If a selected day never had any tasks: simple centered "Keine Aufgaben" message with subtle icon
- First-run empty state (no rooms/tasks at all): keep the current pattern pointing user to create rooms
### Overdue carry-over behavior (CAL-05)
- Overdue tasks (due before today, not yet completed) appear in a separate "Überfällig" section when viewing today
- When viewing past days: show what was due that day (tasks whose nextDueDate matches that day)
- When viewing future days: show only tasks due that day, no overdue carry-over
- Overdue tasks use the existing warm coral/terracotta accent (#E07A5F)
### Claude's Discretion
- Exact scroll range (past and future day count)
- Day card dimensions, spacing, and border radius
- Animation curves and durations beyond the ~200ms auto-scroll
- Floating "Today" button styling and position
- How the celebration state adapts to the calendar context (may simplify from current full-screen version)
- Whether to reuse DailyPlanDao or create a new CalendarDao
- Widget architecture and state management approach
</decisions>
<specifics>
## Specific Ideas
- Day cards should feel like a unified strip with a light green wash — the selected day stands out by being a "marginally stronger green," not a completely different color. Think cohesive gradient, not toggle buttons.
- The selected card is always centered — the strip scrolls to keep the selection in the middle, giving a carousel feel.
- Month labels in the gap between months act as wayfinding, similar to section headers in a contact list.
</specifics>
<code_context>
## Existing Code Insights
### Reusable Assets
- `DailyPlanTaskRow`: Existing task row widget — can be adapted by removing the relative date display and keeping name + room tag + checkbox
- `_CompletingTaskRow`: Animated slide-out on task completion — reuse directly for calendar task list
- `ProgressCard`: Will NOT be used in the new view, but the pattern of a card above a list is established
- `_overdueColor` (#E07A5F): Warm coral constant already defined for overdue indicators — reuse as-is
- `TaskWithRoom` model: Pairs task with room name/ID — directly usable for calendar task list
### Established Patterns
- Riverpod `StreamProvider.autoDispose` for reactive data (see `dailyPlanProvider`) — calendar provider follows same pattern
- Manual provider definition (not `@riverpod`) because of drift's generated types — same constraint applies
- Feature folder structure: `features/home/data/`, `domain/`, `presentation/` — new calendar code lives here (replaces daily plan)
- German-only localization via `.arb` files and `AppLocalizations`
### Integration Points
- `HomeScreen` at route `/` in `router.dart` — the calendar screen replaces this widget entirely
- `AppShell` with bottom NavigationBar — home tab stays as-is, just the screen content changes
- `DailyPlanDao.watchAllTasksWithRoomName()` — returns all tasks sorted by nextDueDate; may need a new query or adapted filtering for arbitrary date selection
- `TaskActionsProvider``completeTask(taskId)` already handles task completion and nextDueDate advancement
- `AppDatabase` with `DailyPlanDao` registered — any new DAO must be registered here
</code_context>
<deferred>
## Deferred Ideas
None — discussion stayed within phase scope
</deferred>
---
*Phase: 05-calendar-strip*
*Context gathered: 2026-03-16*

View File

@@ -0,0 +1,312 @@
---
phase: 06-task-history
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- lib/features/tasks/data/tasks_dao.dart
- lib/features/tasks/data/tasks_dao.g.dart
- lib/features/tasks/presentation/task_history_sheet.dart
- lib/features/tasks/presentation/task_form_screen.dart
- lib/features/home/presentation/calendar_task_row.dart
- lib/l10n/app_de.arb
- lib/l10n/app_localizations.dart
- lib/l10n/app_localizations_de.dart
- test/features/tasks/data/task_history_dao_test.dart
autonomous: true
requirements: [HIST-01, HIST-02]
must_haves:
truths:
- "Every task completion is recorded with a timestamp and persists across app restarts"
- "User can open a history view from the task edit form showing all past completion dates in reverse-chronological order"
- "History view shows a meaningful empty state if the task has never been completed"
artifacts:
- path: "lib/features/tasks/data/tasks_dao.dart"
provides: "watchCompletionsForTask(int taskId) stream method"
contains: "watchCompletionsForTask"
- path: "lib/features/tasks/presentation/task_history_sheet.dart"
provides: "Bottom sheet displaying task completion history"
exports: ["showTaskHistorySheet"]
- path: "lib/features/tasks/presentation/task_form_screen.dart"
provides: "Verlauf button in edit mode opening history sheet"
contains: "showTaskHistorySheet"
- path: "lib/features/home/presentation/calendar_task_row.dart"
provides: "onTap navigation to task edit form"
contains: "context.go"
- path: "test/features/tasks/data/task_history_dao_test.dart"
provides: "Tests for completion history DAO query"
min_lines: 30
key_links:
- from: "lib/features/tasks/presentation/task_form_screen.dart"
to: "lib/features/tasks/presentation/task_history_sheet.dart"
via: "showTaskHistorySheet call in Verlauf button onTap"
pattern: "showTaskHistorySheet"
- from: "lib/features/tasks/presentation/task_history_sheet.dart"
to: "lib/features/tasks/data/tasks_dao.dart"
via: "watchCompletionsForTask stream consumption"
pattern: "watchCompletionsForTask"
- from: "lib/features/home/presentation/calendar_task_row.dart"
to: "TaskFormScreen"
via: "GoRouter navigation on row tap"
pattern: "context\\.go.*tasks"
---
<objective>
Add task completion history: a DAO query to fetch completions, a bottom sheet to display them, integration into the task edit form, and CalendarTaskRow onTap navigation.
Purpose: Users can see exactly when each task was completed in the past, building trust that the scheduling loop is working correctly.
Output: Working history view accessible from task edit form, completion data surfaced from existing TaskCompletions table.
</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/06-task-history/06-CONTEXT.md
<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
<!-- Executor should use these directly -- no codebase exploration needed. -->
From lib/core/database/database.dart:
```dart
/// TaskCompletions table: records when a task was completed.
class TaskCompletions extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get taskId => integer().references(Tasks, #id)();
DateTimeColumn get completedAt => dateTime()();
}
@DriftDatabase(
tables: [Rooms, Tasks, TaskCompletions],
daos: [RoomsDao, TasksDao, DailyPlanDao, CalendarDao],
)
class AppDatabase extends _$AppDatabase { ... }
```
From lib/features/tasks/data/tasks_dao.dart:
```dart
@DriftAccessor(tables: [Tasks, TaskCompletions])
class TasksDao extends DatabaseAccessor<AppDatabase> with _$TasksDaoMixin {
TasksDao(super.attachedDatabase);
Stream<List<Task>> watchTasksInRoom(int roomId) { ... }
Future<int> insertTask(TasksCompanion task) => into(tasks).insert(task);
Future<bool> updateTask(Task task) => update(tasks).replace(task);
Future<void> deleteTask(int taskId) { ... }
Future<void> completeTask(int taskId, {DateTime? now}) { ... }
Future<int> getOverdueTaskCount(int roomId, {DateTime? today}) { ... }
}
```
From lib/features/tasks/presentation/task_form_screen.dart:
```dart
class TaskFormScreen extends ConsumerStatefulWidget {
final int? roomId;
final int? taskId;
const TaskFormScreen({super.key, this.roomId, this.taskId});
bool get isEditing => taskId != null;
}
// build() returns Scaffold with AppBar + Form > ListView with fields
// In edit mode: _existingTask is loaded via _loadExistingTask()
```
From lib/features/home/presentation/calendar_task_row.dart:
```dart
class CalendarTaskRow extends StatelessWidget {
const CalendarTaskRow({
super.key,
required this.taskWithRoom,
required this.onCompleted,
this.isOverdue = false,
});
final TaskWithRoom taskWithRoom;
final VoidCallback onCompleted;
final bool isOverdue;
}
// TaskWithRoom has: task (Task), roomName (String), roomId (int)
```
From lib/features/home/domain/daily_plan_models.dart:
```dart
class TaskWithRoom {
final Task task;
final String roomName;
final int roomId;
const TaskWithRoom({required this.task, required this.roomName, required this.roomId});
}
```
Bottom sheet pattern from lib/features/rooms/presentation/icon_picker_sheet.dart:
```dart
Future<String?> showIconPickerSheet({
required BuildContext context,
String? selectedIconName,
}) {
return showModalBottomSheet<String>(
context: context,
isScrollControlled: true,
builder: (context) => IconPickerSheet(...),
);
}
// Sheet uses SafeArea > Padding > Column(mainAxisSize: MainAxisSize.min) with drag handle
```
Router pattern from lib/core/router/router.dart:
```dart
// Task edit route: /rooms/:roomId/tasks/:taskId
GoRoute(
path: 'tasks/:taskId',
builder: (context, state) {
final taskId = int.parse(state.pathParameters['taskId']!);
return TaskFormScreen(taskId: taskId);
},
),
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Add DAO query, provider, localization, and tests for completion history</name>
<files>
lib/features/tasks/data/tasks_dao.dart,
lib/features/tasks/data/tasks_dao.g.dart,
lib/l10n/app_de.arb,
lib/l10n/app_localizations.dart,
lib/l10n/app_localizations_de.dart,
test/features/tasks/data/task_history_dao_test.dart
</files>
<behavior>
- watchCompletionsForTask(taskId) returns Stream of TaskCompletion list ordered by completedAt DESC (newest first)
- Empty list returned when no completions exist for a given taskId
- After completeTask(taskId) is called, watchCompletionsForTask(taskId) emits a list containing the new completion with correct timestamp
- Completions for different tasks are isolated (taskId=1 completions do not appear in taskId=2 stream)
- Multiple completions for the same task are all returned in reverse-chronological order
</behavior>
<action>
RED phase:
Create test/features/tasks/data/task_history_dao_test.dart with tests for the behaviors above.
Use the existing in-memory database test pattern: AppDatabase(NativeDatabase.memory()), get TasksDao, insert a room and tasks, then test.
Run tests -- they MUST fail (watchCompletionsForTask does not exist yet).
GREEN phase:
1. In lib/features/tasks/data/tasks_dao.dart, add:
```dart
/// Watch all completions for a task, newest first.
Stream<List<TaskCompletion>> watchCompletionsForTask(int taskId) {
return (select(taskCompletions)
..where((c) => c.taskId.equals(taskId))
..orderBy([(c) => OrderingTerm.desc(c.completedAt)]))
.watch();
}
```
2. Run `dart run build_runner build --delete-conflicting-outputs` to regenerate tasks_dao.g.dart.
3. Run tests -- they MUST pass.
Then add localization strings to lib/l10n/app_de.arb:
- "taskHistoryTitle": "Verlauf"
- "taskHistoryEmpty": "Noch nie erledigt"
- "taskHistoryCount": "{count} Mal erledigt" with @taskHistoryCount placeholder for count (int)
Run `flutter gen-l10n` to regenerate app_localizations.dart and app_localizations_de.dart.
NOTE: No separate Riverpod provider is needed -- the bottom sheet will access the DAO directly via appDatabaseProvider (same pattern as _loadExistingTask in TaskFormScreen). This keeps it simple since the sheet is a one-shot modal, not a long-lived screen.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/data/task_history_dao_test.dart -r expanded && flutter analyze --no-fatal-infos</automated>
</verify>
<done>
watchCompletionsForTask method exists on TasksDao, returns Stream of completions sorted newest-first.
All new DAO tests pass. All 101+ existing tests still pass.
Three German localization strings (taskHistoryTitle, taskHistoryEmpty, taskHistoryCount) are available via AppLocalizations.
</done>
</task>
<task type="auto">
<name>Task 2: Build history bottom sheet, wire into TaskFormScreen, add CalendarTaskRow navigation</name>
<files>
lib/features/tasks/presentation/task_history_sheet.dart,
lib/features/tasks/presentation/task_form_screen.dart,
lib/features/home/presentation/calendar_task_row.dart
</files>
<action>
1. Create lib/features/tasks/presentation/task_history_sheet.dart:
- Export a top-level function: `Future&lt;void&gt; showTaskHistorySheet({required BuildContext context, required int taskId})`
- Uses `showModalBottomSheet` with `isScrollControlled: true` following icon_picker_sheet.dart pattern
- The sheet widget is a ConsumerWidget (needs ref to access DAO)
- Uses `ref.read(appDatabaseProvider).tasksDao.watchCompletionsForTask(taskId)` wrapped in a StreamBuilder
- Layout: SafeArea > Padding(16) > Column(mainAxisSize: min):
a. Drag handle (same as icon_picker_sheet: Container 32x4, onSurfaceVariant 0.4 alpha, rounded)
b. Title: AppLocalizations.of(context).taskHistoryTitle (i.e. "Verlauf"), titleMedium style
c. Optional: completion count summary below title using taskHistoryCount string -- show only when count > 0
d. SizedBox(height: 16)
e. StreamBuilder on watchCompletionsForTask:
- Loading: Center(CircularProgressIndicator())
- Empty data: centered Column with Icon(Icons.history, size: 48, color: onSurfaceVariant) + SizedBox(8) + Text(taskHistoryEmpty), style: bodyLarge, color: onSurfaceVariant
- Has data: ConstrainedBox(maxHeight: MediaQuery.of(context).size.height * 0.4) > ListView.builder:
Each item: ListTile with leading Icon(Icons.check_circle_outline, color: primary), title: DateFormat('dd.MM.yyyy', 'de').format(completion.completedAt), subtitle: DateFormat('HH:mm', 'de').format(completion.completedAt)
f. SizedBox(height: 8) at bottom
2. Modify lib/features/tasks/presentation/task_form_screen.dart:
- Import task_history_sheet.dart
- In the build() method's ListView children, AFTER the due date picker section and ONLY when `widget.isEditing` is true, add:
```
const SizedBox(height: 24),
const Divider(),
ListTile(
leading: const Icon(Icons.history),
title: Text(l10n.taskHistoryTitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => showTaskHistorySheet(context: context, taskId: widget.taskId!),
),
```
- This adds a "Verlauf" row that opens the history bottom sheet
3. Modify lib/features/home/presentation/calendar_task_row.dart:
- Add an onTap callback to the ListTile that navigates to the task edit form
- The CalendarTaskRow already has access to taskWithRoom.task.id and taskWithRoom.roomId
- Add to ListTile: `onTap: () => context.go('/rooms/${taskWithRoom.roomId}/tasks/${taskWithRoom.task.id}')`
- This enables: CalendarTaskRow tap -> TaskFormScreen (edit mode) -> "Verlauf" button -> history sheet
- Keep the existing onCompleted checkbox behavior unchanged
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test && flutter analyze --no-fatal-infos</automated>
</verify>
<done>
History bottom sheet opens from TaskFormScreen in edit mode via "Verlauf" row.
Sheet shows completion dates in dd.MM.yyyy + HH:mm format, reverse-chronological.
Empty state shows Icons.history + "Noch nie erledigt" message.
CalendarTaskRow tapping navigates to TaskFormScreen for that task.
All existing tests still pass. dart analyze clean.
</done>
</task>
</tasks>
<verification>
Phase 6 verification checks:
1. `flutter test` -- all tests pass (101 existing + new DAO tests)
2. `flutter analyze --no-fatal-infos` -- zero issues
3. Manual flow: Open app > tap a task in calendar > task edit form opens > "Verlauf" row visible > tap it > bottom sheet shows history or empty state
4. Manual flow: Complete a task via checkbox > navigate to that task's edit form > tap "Verlauf" > new completion entry appears with timestamp
</verification>
<success_criteria>
- HIST-01: Task completion recording verified via DAO tests (completions already written by completeTask; new query surfaces them)
- HIST-02: History bottom sheet accessible from task edit form, shows all past completions reverse-chronologically with German date/time formatting, shows meaningful empty state
- CalendarTaskRow tapping navigates to task edit form (history one tap away)
- Zero regressions: all existing tests pass, dart analyze clean
</success_criteria>
<output>
After completion, create `.planning/phases/06-task-history/06-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,131 @@
---
phase: 06-task-history
plan: 01
subsystem: database, ui
tags: [drift, flutter, riverpod, go_router, intl, bottom-sheet, stream]
# Dependency graph
requires:
- phase: 05-calendar-strip
provides: CalendarTaskRow widget and CalendarDayList that render tasks in the home screen
provides:
- watchCompletionsForTask(taskId) DAO stream on TasksDao — sorted newest-first
- task_history_sheet.dart with showTaskHistorySheet() function
- Verlauf ListTile in TaskFormScreen (edit mode) opening history bottom sheet
- CalendarTaskRow onTap navigation to TaskFormScreen for the tapped task
affects: [07-task-sorting, future-phases-using-TaskFormScreen]
# Tech tracking
tech-stack:
added: []
patterns:
- "Bottom sheet follows icon_picker_sheet pattern: showModalBottomSheet with isScrollControlled, ConsumerWidget inside, SafeArea > Padding > Column(mainAxisSize.min)"
- "StreamBuilder on DAO stream directly accessed via ref.read(appDatabaseProvider).tasksDao.methodName (no separate Riverpod provider for one-shot modals)"
- "TDD: RED test commit followed by GREEN implementation commit"
key-files:
created:
- lib/features/tasks/presentation/task_history_sheet.dart
- test/features/tasks/data/task_history_dao_test.dart
modified:
- lib/features/tasks/data/tasks_dao.dart
- lib/features/tasks/data/tasks_dao.g.dart
- lib/features/tasks/presentation/task_form_screen.dart
- lib/features/home/presentation/calendar_task_row.dart
- lib/l10n/app_de.arb
- lib/l10n/app_localizations.dart
- lib/l10n/app_localizations_de.dart
key-decisions:
- "No separate Riverpod provider for history sheet — ref.read(appDatabaseProvider) directly in ConsumerWidget keeps it simple for a one-shot modal"
- "CalendarTaskRow onTap routes to /rooms/:roomId/tasks/:taskId so history is always one tap away from the home screen"
- "Count summary line shown above list when completions > 0; not shown for empty state"
patterns-established:
- "History sheet: showModalBottomSheet returning Future<void>, ConsumerWidget sheet with StreamBuilder on DAO stream"
- "Edit-mode-only ListTile pattern: if (widget.isEditing) [...] in TaskFormScreen ListView children"
requirements-completed: [HIST-01, HIST-02]
# Metrics
duration: 5min
completed: 2026-03-16
---
# Phase 6 Plan 1: Task History Summary
**Drift DAO stream for task completion history, bottom sheet with reverse-chronological German-formatted dates, wired from CalendarTaskRow tap through TaskFormScreen Verlauf button**
## Performance
- **Duration:** 5 min
- **Started:** 2026-03-16T20:52:49Z
- **Completed:** 2026-03-16T20:57:19Z
- **Tasks:** 2 (Task 1 TDD: RED + GREEN + localization; Task 2: sheet + wiring + navigation)
- **Files modified:** 9
## Accomplishments
- `watchCompletionsForTask(int taskId)` added to TasksDao: returns `Stream<List<TaskCompletion>>` sorted by completedAt DESC
- Task history bottom sheet (`task_history_sheet.dart`) with StreamBuilder, empty state, German date/time formatting via intl
- Verlauf ListTile added to TaskFormScreen edit mode, opens history sheet on tap
- CalendarTaskRow gains `onTap` that navigates via GoRouter to the task edit form, making history one tap away from the calendar
## Task Commits
Each task was committed atomically:
1. **RED - Failing DAO tests** - `2687f5e` (test)
2. **Task 1: DAO method, localization** - `ceae7d7` (feat)
3. **Task 2: History sheet, form wiring, navigation** - `9f902ff` (feat)
**Plan metadata:** (docs commit — see below)
_Note: TDD tasks have separate RED (test) and GREEN (feat) commits_
## Files Created/Modified
- `lib/features/tasks/data/tasks_dao.dart` - Added watchCompletionsForTask stream method
- `lib/features/tasks/data/tasks_dao.g.dart` - Regenerated by build_runner
- `lib/features/tasks/presentation/task_history_sheet.dart` - New: bottom sheet with StreamBuilder, empty state, completion list
- `lib/features/tasks/presentation/task_form_screen.dart` - Added Verlauf ListTile in edit mode
- `lib/features/home/presentation/calendar_task_row.dart` - Added onTap navigation to task edit form
- `lib/l10n/app_de.arb` - Added taskHistoryTitle, taskHistoryEmpty, taskHistoryCount strings
- `lib/l10n/app_localizations.dart` - Regenerated (abstract class updated)
- `lib/l10n/app_localizations_de.dart` - Regenerated (German implementation updated)
- `test/features/tasks/data/task_history_dao_test.dart` - New: 5 tests covering empty state, single/multiple completions, task isolation, stream reactivity
## Decisions Made
- No separate Riverpod provider for history sheet: `ref.read(appDatabaseProvider).tasksDao.watchCompletionsForTask(taskId)` directly in the ConsumerWidget. One-shot modals do not need a dedicated provider.
- CalendarTaskRow navigation uses `context.go('/rooms/.../tasks/...')` consistent with existing GoRouter route patterns.
- Removed unused `import 'package:drift/drift.dart'` from test file (Rule 1 auto-fix during GREEN verification).
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Removed unused import from test file**
- **Found during:** Task 1 (GREEN phase, flutter analyze)
- **Issue:** `import 'package:drift/drift.dart'` was copied from the existing tasks_dao_test.dart pattern but not needed in the new history test file (no `Value()` usage)
- **Fix:** Removed the unused import line
- **Files modified:** test/features/tasks/data/task_history_dao_test.dart
- **Verification:** flutter analyze reports zero issues
- **Committed in:** ceae7d7 (Task 1 feat commit)
---
**Total deviations:** 1 auto-fixed (1 bug — unused import)
**Impact on plan:** Trivial cleanup. No scope creep.
## Issues Encountered
None — plan executed smoothly. All 106 tests pass (101 pre-existing + 5 new DAO tests), zero analyze issues.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Phase 6 Plan 1 complete. Task history is fully functional.
- Phase 7 (task sorting) can proceed independently.
- No blockers.
---
*Phase: 06-task-history*
*Completed: 2026-03-16*

View File

@@ -0,0 +1,81 @@
# Phase 6: Task History - Context
**Gathered:** 2026-03-16
**Status:** Ready for planning
<domain>
## Phase Boundary
Let users view past completion dates for any individual task. The data layer already records completions (TaskCompletions table + completeTask writes timestamps). This phase adds a DAO query and a UI to surface that data. Requirements: HIST-01 (verify recording works), HIST-02 (view history).
</domain>
<decisions>
## Implementation Decisions
### Entry point
- From the task edit form (TaskFormScreen) in edit mode: add a "Verlauf" (History) button/row that opens the history view
- From CalendarTaskRow: add onTap to navigate to the task edit form (currently only has checkbox) — history is then one tap away
- No long-press or context menu — keep interaction model simple and consistent
### History view format
- Bottom sheet (showModalBottomSheet) — consistent with existing template_picker_sheet and icon_picker_sheet patterns
- Each entry shows: date formatted as "dd.MM.yyyy" and time as "HH:mm" — German locale
- Entries listed reverse-chronological (newest first)
- No grouping or pagination — household tasks won't have thousands of completions; simple ListView is sufficient
### Empty state
- When task has never been completed: centered icon (e.g., Icons.history) + "Noch nie erledigt" message — meaningful, not just blank
- No special state for many completions — just scroll
### Claude's Discretion
- Exact bottom sheet height and styling
- Whether to show a completion count summary at the top of the sheet
- Animation and transition details
- DAO query structure (single method returning List<TaskCompletion>)
- Whether CalendarTaskRow onTap goes to edit form or directly to history
</decisions>
<specifics>
## Specific Ideas
No specific requirements — user chose "You decide." Open to standard approaches that match existing app patterns.
</specifics>
<code_context>
## Existing Code Insights
### Reusable Assets
- `TaskCompletions` table: Already exists in database.dart (id, taskId, completedAt) — no schema change needed
- `TasksDao.completeTask()`: Already inserts into taskCompletions on every completion — HIST-01 data recording is done
- `showModalBottomSheet`: Used by template_picker_sheet.dart and icon_picker_sheet.dart — established pattern for overlays
- `AppLocalizations` + `.arb` files: German-only localization pipeline in place
### Established Patterns
- DAOs extend `DatabaseAccessor<AppDatabase>` with `@DriftAccessor` annotation
- Riverpod `StreamProvider.autoDispose` or `FutureProvider` for reactive data
- Feature folder structure: `features/tasks/data/`, `domain/`, `presentation/`
- Bottom sheets use `showModalBottomSheet` with `DraggableScrollableSheet` or simple `Column`
### Integration Points
- `TaskFormScreen` (edit mode): Entry point for history — add a row/button when `isEditing`
- `TasksDao`: Add `watchCompletionsForTask(int taskId)` or `getCompletionsForTask(int taskId)` method
- `CalendarTaskRow`: Currently no onTap — needs navigation to task edit form for history access
- `router.dart`: Route `/rooms/:roomId/tasks/:taskId` already exists for TaskFormScreen — no new route needed if using bottom sheet
- `app_de.arb`: Add localization strings for history UI labels
</code_context>
<deferred>
## Deferred Ideas
None — discussion stayed within phase scope
</deferred>
---
*Phase: 06-task-history*
*Context gathered: 2026-03-16*

View File

@@ -0,0 +1,114 @@
---
phase: 06-task-history
verified: 2026-03-16T22:15:00Z
status: passed
score: 3/3 must-haves verified
re_verification: false
---
# Phase 6: Task History Verification Report
**Phase Goal:** Users can see exactly when each task was completed in the past, building trust that the scheduling loop is working correctly
**Verified:** 2026-03-16T22:15:00Z
**Status:** PASSED
**Re-verification:** No — initial verification
---
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Every task completion is recorded with a timestamp and persists across app restarts | VERIFIED | `watchCompletionsForTask` reads from `TaskCompletions` table (persistent SQLite); `completeTask` already wrote timestamps; 5 DAO tests confirm stream returns correct data including stream reactivity test |
| 2 | User can open a history view from the task edit form showing all past completion dates in reverse-chronological order | VERIFIED | `task_form_screen.dart` lines 192-204: `if (widget.isEditing)` guard shows `ListTile` with `onTap: () => showTaskHistorySheet(...)`. Sheet uses `StreamBuilder` on `watchCompletionsForTask` with `..orderBy([(c) => OrderingTerm.desc(c.completedAt)])`, renders dates as `dd.MM.yyyy` + `HH:mm` via intl |
| 3 | History view shows a meaningful empty state if the task has never been completed | VERIFIED | `task_history_sheet.dart` lines 70-87: `if (completions.isEmpty)` branch renders `Icon(Icons.history, size: 48)` + `Text(l10n.taskHistoryEmpty)` ("Noch nie erledigt") |
**Score:** 3/3 truths verified
---
### Required Artifacts
| Artifact | Provides | Status | Details |
|----------|---------|--------|---------|
| `lib/features/tasks/data/tasks_dao.dart` | `watchCompletionsForTask(int taskId)` stream method | VERIFIED | Method exists at line 85, returns `Stream<List<TaskCompletion>>`, ordered by `completedAt DESC`, 110 lines total |
| `lib/features/tasks/presentation/task_history_sheet.dart` | Bottom sheet displaying task completion history | VERIFIED | 137 lines, exports top-level `showTaskHistorySheet()`, `_TaskHistorySheet` is a `ConsumerWidget` with full StreamBuilder, empty state, date list |
| `lib/features/tasks/presentation/task_form_screen.dart` | Verlauf button in edit mode opening history sheet | VERIFIED | Imports `task_history_sheet.dart` (line 13), `showTaskHistorySheet` called at line 199, guarded by `if (widget.isEditing)` |
| `lib/features/home/presentation/calendar_task_row.dart` | onTap navigation to task edit form | VERIFIED | `ListTile.onTap` at line 39 calls `context.go('/rooms/${taskWithRoom.roomId}/tasks/${taskWithRoom.task.id}')` |
| `test/features/tasks/data/task_history_dao_test.dart` | Tests for completion history DAO query | VERIFIED | 158 lines, 5 tests: empty state, single completion, multiple reverse-chronological, task isolation, stream reactivity — all pass |
| `lib/features/tasks/data/tasks_dao.g.dart` | Drift-generated mixin (build_runner output) | VERIFIED | Exists, 25 lines, regenerated with `taskCompletions` table accessor present |
---
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `task_form_screen.dart` | `task_history_sheet.dart` | `showTaskHistorySheet` call in Verlauf `onTap` | WIRED | Import at line 13; called at line 199 inside `if (widget.isEditing)` block |
| `task_history_sheet.dart` | `tasks_dao.dart` | `watchCompletionsForTask` stream consumption | WIRED | `ref.read(appDatabaseProvider).tasksDao.watchCompletionsForTask(taskId)` at lines 59-62; stream result consumed by `StreamBuilder` builder |
| `calendar_task_row.dart` | `TaskFormScreen` | GoRouter navigation on row tap | WIRED | `context.go('/rooms/${taskWithRoom.roomId}/tasks/${taskWithRoom.task.id}')` at line 39-41; route `/rooms/:roomId/tasks/:taskId` resolves to `TaskFormScreen` per router.dart |
---
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|------------|-------------|--------|----------|
| HIST-01 | 06-01-PLAN.md | Each task completion is recorded with a timestamp | SATISFIED | `TasksDao.completeTask()` inserts into `TaskCompletions` (pre-existing); `watchCompletionsForTask` surfaces data; 5 DAO tests confirm timestamps are stored and retrieved correctly |
| HIST-02 | 06-01-PLAN.md | User can view past completion dates for any individual task | SATISFIED | Full UI chain: `CalendarTaskRow.onTap` -> `TaskFormScreen` (edit mode) -> "Verlauf" `ListTile` -> `showTaskHistorySheet` -> `_TaskHistorySheet` StreamBuilder showing reverse-chronological German-formatted dates |
No orphaned requirements — REQUIREMENTS.md Traceability table shows only HIST-01 and HIST-02 mapped to Phase 6, both accounted for and marked Complete.
---
### Anti-Patterns Found
None. No TODOs, FIXMEs, placeholder returns, empty handlers, or stub implementations found in any of the 5 modified source files.
---
### Human Verification Required
#### 1. Tap-to-edit navigation in running app
**Test:** Launch app, ensure at least one task exists on the calendar, tap the task row (not the checkbox).
**Expected:** App navigates to `TaskFormScreen` in edit mode showing the task's fields and a "Verlauf" row at the bottom.
**Why human:** GoRouter navigation with `context.go` cannot be verified by static analysis; requires runtime rendering.
#### 2. History sheet opens with correct content
**Test:** In `TaskFormScreen` edit mode, tap the "Verlauf" ListTile.
**Expected:** Bottom sheet slides up showing either: (a) the empty state with a history icon and "Noch nie erledigt", or (b) a list of past completions with `dd.MM.yyyy` dates as titles and `HH:mm` times as subtitles, newest first.
**Why human:** `showModalBottomSheet` rendering and visual layout cannot be verified by static analysis.
#### 3. Live update after completing a task
**Test:** Complete a task via checkbox in the calendar, then navigate to that task's edit form and tap "Verlauf".
**Expected:** The newly recorded completion appears at the top of the history sheet with today's date and approximate current time.
**Why human:** Real-time stream reactivity through the full UI stack (checkbox -> DAO write -> stream emit -> sheet UI update) requires runtime observation.
---
### Verification Summary
All automated checks passed with no gaps found.
**Test suite:** 106/106 tests pass (101 pre-existing + 5 new DAO tests covering all specified behaviors).
**Static analysis:** `flutter analyze --no-fatal-infos` — zero issues.
**Commits verified:** All three phase commits exist (`2687f5e`, `ceae7d7`, `9f902ff`) with expected file changes.
The full feature chain is intact:
- `TaskCompletions` table stores timestamps (HIST-01, pre-existing from data layer)
- `watchCompletionsForTask` surfaces completions as a live Drift stream
- `task_history_sheet.dart` renders them in German locale with reverse-chronological ordering and a meaningful empty state
- `TaskFormScreen` (edit mode only) provides the "Verlauf" entry point
- `CalendarTaskRow` onTap makes history reachable from the home calendar in two taps
Three human-only items remain for final sign-off: tap navigation, sheet rendering, and live update after completion.
---
_Verified: 2026-03-16T22:15:00Z_
_Verifier: Claude (gsd-verifier)_

View File

@@ -0,0 +1,276 @@
---
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\\)"
---
<objective>
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.
</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/07-task-sorting/07-CONTEXT.md
<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
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<TaskWithRoom> dayTasks;
final List<TaskWithRoom> 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<void> _loadPersistedThemeMode() async { ... }
Future<void> 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<CalendarDayState>((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<List<Task>, 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<IntervalType>()();
IntColumn get intervalDays => integer().withDefault(const Constant(1))();
IntColumn get effortLevel => intEnum<EffortLevel>()();
}
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create TaskSortOption enum, SortPreferenceNotifier, and localization strings</name>
<files>
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
</files>
<behavior>
- 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
</behavior>
<action>
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)
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test test/features/tasks/presentation/sort_preference_notifier_test.dart && flutter analyze --no-fatal-infos</automated>
</verify>
<done>TaskSortOption enum exists with 3 values. SortPreferenceNotifier persists to SharedPreferences. 3+ unit tests pass. ARB file has 4 new sort strings. dart analyze clean.</done>
</task>
<task type="auto">
<name>Task 2: Integrate sort logic into calendarDayProvider and tasksInRoomProvider</name>
<files>
lib/features/home/presentation/calendar_providers.dart,
lib/features/tasks/presentation/task_providers.dart
</files>
<action>
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<TaskWithRoom> _sortTasks(List<TaskWithRoom> 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<Task> _sortTasksRaw(List<Task> 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).
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test && flutter analyze --no-fatal-infos</automated>
</verify>
<done>calendarDayProvider watches sortPreferenceProvider and sorts dayTasks accordingly. tasksInRoomProvider watches sortPreferenceProvider and sorts tasks accordingly. All 106+ existing tests pass. dart analyze clean.</done>
</task>
</tasks>
<verification>
- `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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/07-task-sorting/07-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,138 @@
---
phase: 07-task-sorting
plan: 01
subsystem: ui
tags: [flutter, riverpod, shared_preferences, sorting, localization]
# Dependency graph
requires:
- phase: 05-calendar-strip
provides: calendarDayProvider and CalendarDayState used by sort integration
- phase: 06-task-history
provides: task domain model and CalendarTaskRow context
provides:
- TaskSortOption enum (alphabetical, interval, effort)
- SortPreferenceNotifier with SharedPreferences persistence
- sortPreferenceProvider (keepAlive Riverpod provider)
- calendarDayProvider with in-memory sort of dayTasks
- tasksInRoomProvider with in-memory sort via stream.map
- German localization strings: sortAlphabetical, sortInterval, sortEffort, sortLabel
affects: [07-02-sort-ui, any phase using calendarDayProvider or tasksInRoomProvider]
# Tech tracking
tech-stack:
added: []
patterns:
- "SortPreferenceNotifier: sync default return, async _loadPersisted() — same pattern as ThemeNotifier"
- "In-memory sort helper functions (_sortTasks, _sortTasksRaw) applied after DB stream emit"
- "overdueTasks intentionally unsorted — only dayTasks sorted"
key-files:
created:
- 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
- test/features/tasks/presentation/sort_preference_notifier_test.dart
modified:
- lib/features/home/presentation/calendar_providers.dart
- lib/features/tasks/presentation/task_providers.dart
- lib/l10n/app_de.arb
- lib/l10n/app_localizations.dart
- lib/l10n/app_localizations_de.dart
key-decisions:
- "Default sort is alphabetical — continuity with existing A-Z SQL sort in CalendarDayList"
- "overdueTasks are NOT sorted — they stay pinned at the top in existing order"
- "Sort stored as string (enum.name) in SharedPreferences — not intEnum, so reordering enum is safe"
- "SortPreferenceNotifier uses keepAlive: true — global preference should never be disposed"
patterns-established:
- "SortPreferenceNotifier pattern: sync default + async _loadPersisted() — matches ThemeNotifier"
- "In-memory sort via stream.map in StreamProvider — DB SQL sort provides stable baseline, in-memory overrides"
requirements-completed: [SORT-01, SORT-02, SORT-03]
# Metrics
duration: 4min
completed: 2026-03-16
---
# Phase 07 Plan 01: Task Sort Domain and Provider Summary
**TaskSortOption enum + SharedPreferences-backed SortPreferenceNotifier wired into calendarDayProvider and tasksInRoomProvider with in-memory alphabetical/interval/effort sorting**
## Performance
- **Duration:** 4 min
- **Started:** 2026-03-16T21:29:32Z
- **Completed:** 2026-03-16T21:33:37Z
- **Tasks:** 2
- **Files modified:** 9
## Accomplishments
- TaskSortOption enum (alphabetical, interval, effort) with SharedPreferences persistence via SortPreferenceNotifier
- calendarDayProvider now watches sortPreferenceProvider and sorts dayTasks in-memory; overdueTasks intentionally unsorted
- tasksInRoomProvider now watches sortPreferenceProvider and applies sort via stream.map
- 7 new unit tests for SortPreferenceNotifier covering default, state update, persistence, and restart recovery
- 4 German localization strings added (sortAlphabetical, sortInterval, sortEffort, sortLabel)
## Task Commits
Each task was committed atomically:
1. **TDD RED: Failing sort preference tests** - `a9f2983` (test)
2. **Task 1: TaskSortOption enum, SortPreferenceNotifier, localization** - `13c7d62` (feat)
3. **Task 2: Sort integration into calendarDayProvider and tasksInRoomProvider** - `3697e4e` (feat)
## Files Created/Modified
- `lib/features/tasks/domain/task_sort_option.dart` - TaskSortOption enum with alphabetical/interval/effort values
- `lib/features/tasks/presentation/sort_preference_notifier.dart` - SortPreferenceNotifier with SharedPreferences persistence
- `lib/features/tasks/presentation/sort_preference_notifier.g.dart` - Generated Riverpod provider code
- `lib/features/home/presentation/calendar_providers.dart` - Added sortPreferenceProvider watch + _sortTasks helper
- `lib/features/tasks/presentation/task_providers.dart` - Added sortPreferenceProvider watch + _sortTasksRaw helper + stream.map
- `lib/l10n/app_de.arb` - Added sortAlphabetical, sortInterval, sortEffort, sortLabel strings
- `lib/l10n/app_localizations.dart` - Regenerated with sort string getters
- `lib/l10n/app_localizations_de.dart` - Regenerated with German sort string implementations
- `test/features/tasks/presentation/sort_preference_notifier_test.dart` - 7 unit tests for sort preference
## Decisions Made
- Default sort is alphabetical for continuity with existing SQL A-Z sort in CalendarDayList
- overdueTasks section is explicitly NOT sorted — stays pinned at top in existing order
- Sort preference stored as enum.name string in SharedPreferences (not intEnum) so enum reordering is always safe
- SortPreferenceNotifier uses `keepAlive: true` — global app preference must not be disposed
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- sortPreferenceProvider is live and defaults to alphabetical
- Both task list providers react to sort preference changes immediately
- Ready for 07-02: sort UI (dropdown in AppBar) to write to sortPreferenceProvider
---
*Phase: 07-task-sorting*
*Completed: 2026-03-16*
## Self-Check: PASSED
- FOUND: lib/features/tasks/domain/task_sort_option.dart
- FOUND: lib/features/tasks/presentation/sort_preference_notifier.dart
- FOUND: lib/features/tasks/presentation/sort_preference_notifier.g.dart
- FOUND: test/features/tasks/presentation/sort_preference_notifier_test.dart
- FOUND: .planning/phases/07-task-sorting/07-01-SUMMARY.md
- Commits a9f2983, 13c7d62, 3697e4e all verified in git log

View File

@@ -0,0 +1,214 @@
---
phase: 07-task-sorting
plan: 02
type: execute
wave: 2
depends_on: ["07-01"]
files_modified:
- lib/features/tasks/presentation/sort_dropdown.dart
- lib/features/home/presentation/home_screen.dart
- lib/features/tasks/presentation/task_list_screen.dart
- test/features/home/presentation/home_screen_test.dart
autonomous: true
requirements: [SORT-01, SORT-02, SORT-03]
must_haves:
truths:
- "A sort dropdown is visible in the HomeScreen AppBar showing the current sort label"
- "A sort dropdown is visible in the TaskListScreen AppBar showing the current sort label"
- "Tapping the dropdown shows three options: A-Z, Intervall, Aufwand"
- "Selecting a sort option updates the task list order immediately"
- "The sort preference persists across screen navigations and app restarts"
artifacts:
- path: "lib/features/tasks/presentation/sort_dropdown.dart"
provides: "Reusable SortDropdown ConsumerWidget"
exports: ["SortDropdown"]
- path: "lib/features/home/presentation/home_screen.dart"
provides: "HomeScreen with AppBar containing SortDropdown"
contains: "SortDropdown"
- path: "lib/features/tasks/presentation/task_list_screen.dart"
provides: "TaskListScreen AppBar with SortDropdown alongside edit/delete"
contains: "SortDropdown"
key_links:
- from: "lib/features/tasks/presentation/sort_dropdown.dart"
to: "sortPreferenceProvider"
via: "ref.watch for display, ref.read for mutation"
pattern: "ref\\.watch\\(sortPreferenceProvider\\)"
- from: "lib/features/home/presentation/home_screen.dart"
to: "lib/features/tasks/presentation/sort_dropdown.dart"
via: "SortDropdown widget in AppBar actions"
pattern: "SortDropdown"
- from: "lib/features/tasks/presentation/task_list_screen.dart"
to: "lib/features/tasks/presentation/sort_dropdown.dart"
via: "SortDropdown widget in AppBar actions"
pattern: "SortDropdown"
---
<objective>
Build the sort dropdown widget and wire it into both task list screens (HomeScreen and TaskListScreen), adding an AppBar to HomeScreen.
Purpose: Gives users visible access to the sort controls. The data layer from Plan 01 already sorts reactively; this plan adds the UI trigger.
Output: SortDropdown reusable widget, updated HomeScreen with AppBar, updated TaskListScreen with dropdown in existing AppBar, updated 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/07-task-sorting/07-CONTEXT.md
@.planning/phases/07-task-sorting/07-01-SUMMARY.md
<interfaces>
<!-- Interfaces from Plan 01 that this plan depends on -->
From lib/features/tasks/domain/task_sort_option.dart (created in 07-01):
```dart
enum TaskSortOption { alphabetical, interval, effort }
```
From lib/features/tasks/presentation/sort_preference_notifier.dart (created in 07-01):
```dart
@riverpod
class SortPreferenceNotifier extends _$SortPreferenceNotifier {
TaskSortOption build(); // returns alphabetical by default
Future<void> setSortOption(TaskSortOption option);
}
// Generated as: sortPreferenceProvider
```
From lib/l10n/app_de.arb (strings added in 07-01):
```
sortAlphabetical: "A-Z"
sortInterval: "Intervall"
sortEffort: "Aufwand"
sortLabel: "Sortierung"
```
<!-- Existing interfaces being modified -->
From lib/features/home/presentation/home_screen.dart:
```dart
class HomeScreen extends ConsumerStatefulWidget {
// Currently: Stack with CalendarStrip + CalendarDayList + floating Today FAB
// No AppBar — body sits directly inside AppShell's Scaffold
}
```
From lib/features/tasks/presentation/task_list_screen.dart:
```dart
class TaskListScreen extends ConsumerWidget {
// Has its own Scaffold with AppBar containing edit + delete IconButtons
// AppBar actions: [edit, delete]
}
```
From lib/features/home/presentation/calendar_strip.dart:
```dart
class CalendarStrip extends StatefulWidget {
const CalendarStrip({super.key, required this.controller, this.onTodayVisibilityChanged});
final CalendarStripController controller;
final ValueChanged<bool>? onTodayVisibilityChanged;
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Build SortDropdown widget and integrate into HomeScreen and TaskListScreen</name>
<files>
lib/features/tasks/presentation/sort_dropdown.dart,
lib/features/home/presentation/home_screen.dart,
lib/features/tasks/presentation/task_list_screen.dart
</files>
<action>
1. Create `lib/features/tasks/presentation/sort_dropdown.dart`:
- A `ConsumerWidget` named `SortDropdown`
- Uses `PopupMenuButton<TaskSortOption>` (Material 3, better than DropdownButton for AppBar trailing actions — it opens a menu overlay rather than inline expansion)
- `ref.watch(sortPreferenceProvider)` to get current sort option
- The button child shows the current sort label as a Text widget using l10n strings:
- `alphabetical` -> `l10n.sortAlphabetical` (A-Z)
- `interval` -> `l10n.sortInterval` (Intervall)
- `effort` -> `l10n.sortEffort` (Aufwand)
- Style the button child as a Row with `Icon(Icons.sort)` + `SizedBox(width: 4)` + label Text. Use `theme.textTheme.labelLarge` for the text.
- `itemBuilder` returns 3 `PopupMenuItem<TaskSortOption>` entries with check marks: for each option, show a Row with `Icon(Icons.check, size: 18)` (visible only when selected, invisible when not via `Opacity(opacity: isSelected ? 1 : 0)`) + `SizedBox(width: 8)` + label Text
- `onSelected`: `ref.read(sortPreferenceProvider.notifier).setSortOption(value)`
- Helper method `String _label(TaskSortOption option, AppLocalizations l10n)` that maps enum to l10n string
2. Edit `lib/features/home/presentation/home_screen.dart`:
- HomeScreen currently returns a `Stack` with `Column(CalendarStrip, Expanded(CalendarDayList))` + optional floating Today button
- Wrap the entire current Stack in a `Scaffold` with an `AppBar`:
- `AppBar(title: Text(l10n.tabHome), actions: [const SortDropdown()])`
- The `tabHome` l10n string already exists ("Ubersicht") — reuse it as the AppBar title for the home screen
- body: the existing Stack content
- Keep CalendarStrip, CalendarDayList, and floating Today FAB exactly as they are
- Import `sort_dropdown.dart`
- Note: HomeScreen is inside AppShell's Scaffold body. Adding a nested Scaffold is fine and standard for per-tab AppBars in StatefulShellRoute.indexedStack. The AppShell Scaffold provides the bottom nav; the inner Scaffold provides the AppBar.
3. Edit `lib/features/tasks/presentation/task_list_screen.dart`:
- In the existing `AppBar.actions` list, add `const SortDropdown()` BEFORE the edit and delete IconButtons. Order: [SortDropdown, edit, delete].
- Import `sort_dropdown.dart`
- No other changes to TaskListScreen
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos</automated>
</verify>
<done>SortDropdown widget exists showing current sort label with sort icon. HomeScreen has AppBar with title "Ubersicht" and SortDropdown. TaskListScreen AppBar has SortDropdown before edit/delete buttons. dart analyze clean.</done>
</task>
<task type="auto">
<name>Task 2: Update tests for HomeScreen AppBar and sort dropdown</name>
<files>
test/features/home/presentation/home_screen_test.dart
</files>
<action>
1. Edit `test/features/home/presentation/home_screen_test.dart`:
- Add import for `sort_preference_notifier.dart` and `task_sort_option.dart`
- In the `_buildApp` helper, add a provider override for `sortPreferenceProvider`:
```dart
sortPreferenceProvider.overrideWith(SortPreferenceNotifier.new),
```
This will use the real notifier with mock SharedPreferences (already set up in setUp).
- Add a new test group `'HomeScreen sort dropdown'`:
- Test: "shows sort dropdown in AppBar" — pump the app with tasks, verify `find.byType(PopupMenuButton<TaskSortOption>)` findsOneWidget
- Test: "shows AppBar with title" — verify `find.text('Ubersicht')` findsOneWidget (the tabHome l10n string)
- Verify all existing tests still pass. The addition of an AppBar wrapping the existing content should not break existing assertions since they look for specific widgets/text within the tree.
2. Run full test suite to confirm no regressions.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test && flutter analyze --no-fatal-infos</automated>
</verify>
<done>Home screen tests verify AppBar with sort dropdown is present. All 108+ tests pass (106 existing + 2+ new). dart analyze clean.</done>
</task>
</tasks>
<verification>
- `flutter test` — all tests pass including new sort dropdown tests
- `flutter analyze --no-fatal-infos` — zero issues
- HomeScreen has AppBar with SortDropdown visible
- TaskListScreen has SortDropdown in AppBar actions
- Tapping dropdown shows 3 options with check mark on current selection
- Selecting a different sort option reorders the task list reactively
</verification>
<success_criteria>
- SortDropdown widget is reusable and shows current sort with icon
- HomeScreen has AppBar titled "Ubersicht" with SortDropdown in trailing actions
- TaskListScreen has SortDropdown before edit/delete buttons in AppBar
- Sort selection updates task list order immediately (reactive via provider)
- Sort preference persists (set in one screen, visible in another after navigation)
- All tests pass, analyze clean
</success_criteria>
<output>
After completion, create `.planning/phases/07-task-sorting/07-02-SUMMARY.md`
</output>

View File

@@ -0,0 +1,125 @@
---
phase: 07-task-sorting
plan: 02
subsystem: ui
tags: [flutter, riverpod, material3, popup-menu, sort-ui, localization]
# Dependency graph
requires:
- phase: 07-task-sorting
plan: 01
provides: sortPreferenceProvider, TaskSortOption enum, German sort l10n strings
provides:
- SortDropdown ConsumerWidget (PopupMenuButton<TaskSortOption> with check marks)
- HomeScreen with AppBar (title: Übersicht, actions: SortDropdown)
- TaskListScreen AppBar with SortDropdown before edit/delete buttons
affects: [home_screen_test.dart, app_shell_test.dart, any screen showing HomeScreen]
# Tech tracking
tech-stack:
added: []
patterns:
- "PopupMenuButton<TaskSortOption> with Opacity check mark — avoids layout shift vs conditional Icon"
- "Nested Scaffold inside AppShell tab body — standard pattern for per-tab AppBars in StatefulShellRoute"
key-files:
created:
- lib/features/tasks/presentation/sort_dropdown.dart
modified:
- lib/features/home/presentation/home_screen.dart
- lib/features/tasks/presentation/task_list_screen.dart
- test/features/home/presentation/home_screen_test.dart
- test/shell/app_shell_test.dart
key-decisions:
- "Used PopupMenuButton instead of DropdownButton for AppBar — menu overlay vs inline expansion, consistent with Material 3 AppBar action patterns"
- "Opacity(opacity: isSelected ? 1 : 0) for check mark — preserves item width alignment vs conditional show/hide"
- "HomeScreen Scaffold is nested inside AppShell Scaffold — standard StatefulShellRoute pattern for per-tab AppBars"
# Metrics
duration: 4min
completed: 2026-03-16
---
# Phase 07 Plan 02: Sort Dropdown UI Summary
**SortDropdown ConsumerWidget using PopupMenuButton wired into HomeScreen AppBar (title: Übersicht) and TaskListScreen AppBar before edit/delete actions**
## Performance
- **Duration:** 4 min
- **Started:** 2026-03-16T21:35:56Z
- **Completed:** 2026-03-16T21:39:24Z
- **Tasks:** 2
- **Files modified:** 5 (1 created, 4 modified)
## Accomplishments
- SortDropdown ConsumerWidget: PopupMenuButton<TaskSortOption> with sort icon, current label, and check mark on active option
- HomeScreen wrapped in Scaffold with AppBar titled "Übersicht" and SortDropdown in trailing actions
- TaskListScreen AppBar has SortDropdown before the existing edit/delete IconButtons
- 2 new tests in HomeScreen test suite: verifies PopupMenuButton and AppBar title presence
- Auto-fixed app_shell_test regression caused by "Übersicht" now appearing twice (AppBar + bottom nav)
## Task Commits
Each task was committed atomically:
1. **Task 1: SortDropdown widget and HomeScreen/TaskListScreen integration** - `e5eccb7` (feat)
2. **Task 2: Sort dropdown tests and AppShell test fix** - `a3e4d02` (feat)
## Files Created/Modified
- `lib/features/tasks/presentation/sort_dropdown.dart` - Reusable SortDropdown ConsumerWidget with PopupMenuButton<TaskSortOption>
- `lib/features/home/presentation/home_screen.dart` - Added Scaffold with AppBar (Übersicht title + SortDropdown)
- `lib/features/tasks/presentation/task_list_screen.dart` - Added SortDropdown before edit/delete in AppBar actions
- `test/features/home/presentation/home_screen_test.dart` - Added sortPreferenceProvider override + 2 new sort dropdown tests
- `test/shell/app_shell_test.dart` - Fixed findsOneWidget -> findsWidgets for 'Übersicht' (now in AppBar + bottom nav)
## Decisions Made
- Used PopupMenuButton instead of DropdownButton for AppBar actions — menu overlay is cleaner in AppBar context (Material 3)
- Opacity trick for check mark: `Opacity(opacity: isSelected ? 1 : 0)` preserves item width so labels align regardless of selection
- HomeScreen uses nested Scaffold for AppBar — standard pattern in StatefulShellRoute.indexedStack; AppShell provides bottom nav, HomeScreen provides AppBar
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Fixed AppShell test regression from 'Übersicht' duplicate**
- **Found during:** Task 2 test run
- **Issue:** `app_shell_test.dart` expected `findsOneWidget` for 'Übersicht'. Adding the HomeScreen AppBar title caused the string to appear twice (AppBar + bottom nav label).
- **Fix:** Changed `findsOneWidget` to `findsWidgets` in `app_shell_test.dart` line 67. Applied same fix to new `home_screen_test.dart` AppBar title test.
- **Files modified:** `test/shell/app_shell_test.dart`, `test/features/home/presentation/home_screen_test.dart`
- **Commit:** `a3e4d02`
## Issues Encountered
None beyond the auto-fixed regression.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Phase 07 (task sorting) is now complete: data layer (07-01) + UI layer (07-02)
- Sort dropdown is live in both HomeScreen and TaskListScreen AppBars
- Selecting a sort option reactively reorders task lists via sortPreferenceProvider
- Preference persists across app restarts via SharedPreferences
---
*Phase: 07-task-sorting*
*Completed: 2026-03-16*
## Self-Check: PASSED
- FOUND: lib/features/tasks/presentation/sort_dropdown.dart
- FOUND: lib/features/home/presentation/home_screen.dart (modified)
- FOUND: lib/features/tasks/presentation/task_list_screen.dart (modified)
- FOUND: test/features/home/presentation/home_screen_test.dart (modified)
- FOUND: test/shell/app_shell_test.dart (modified)
- Commits e5eccb7, a3e4d02 verified in git log
- All 115 tests pass, dart analyze clean

View File

@@ -0,0 +1,91 @@
# Phase 7: Task Sorting - Context
**Gathered:** 2026-03-16
**Status:** Ready for planning
<domain>
## Phase Boundary
Add sort controls to task list screens so users can reorder tasks by name (alphabetical), frequency interval, or effort level. The sort preference persists across app restarts. Requirements: SORT-01, SORT-02, SORT-03.
</domain>
<decisions>
## Implementation Decisions
### Sort control widget
- Dropdown button in the AppBar, right side (trailing actions position)
- When collapsed, shows the current sort name as text (e.g., "A-Z", "Intervall", "Aufwand")
- Expands to show the 3 sort options as a standard dropdown menu
### Sort option labels
- Claude's discretion — pick German labels that fit the app's existing localization style (concise but clear)
### Sort scope
- One global sort preference applies to all task list screens
- Same dropdown appears in both the home screen (CalendarDayList) and per-room (TaskListScreen) AppBars
### Persistence
- Store the sort preference in SharedPreferences (simple key-value for a single enum)
- No database schema change needed
- Persists across app restarts per success criteria
### Default sort
- Claude's discretion — pick the least disruptive default (likely alphabetical to match current CalendarDayList behavior)
### Claude's Discretion
- Sort option label text (German, concise)
- Default sort order (recommend alphabetical for continuity)
- Whether TaskListScreen also gets the dropdown (recommend yes for consistency with global setting, since the success criteria says "task list screens" plural)
- Sort direction (always ascending — A-Z, daily→yearly, low→high — no toggle needed for MVP)
- Dropdown styling (Material 3 DropdownButton or PopupMenuButton variant)
- Sort icon or visual indicator in the dropdown
- How overdue section interacts with sorting (recommend: overdue section stays pinned at top regardless of sort, only day tasks are sorted)
</decisions>
<specifics>
## Specific Ideas
No specific requirements — open to standard approaches that match existing app patterns.
</specifics>
<code_context>
## Existing Code Insights
### Reusable Assets
- `EffortLevel` enum (low/medium/high) with `.index` for ordering — directly usable for effort sort
- `IntervalType` enum with `.index` ordered roughly by frequency (daily=0 through yearly=7) — usable for interval sort
- `FrequencyInterval.presets` list ordered most-frequent to least — reference for sort order
- `Task.name` field — direct alphabetical sort target
- `CalendarDayList` and `TaskListScreen` — the two list widgets that need sort integration
- `AppLocalizations` + `.arb` files — existing German localization pipeline
### Established Patterns
- Manual `StreamProvider.autoDispose` for drift types (riverpod_generator issue) — sort provider follows same pattern
- `calendarDayProvider` watches `selectedDateProvider` — can also watch a sort preference provider
- `tasksInRoomProvider` family provider — can be extended with sort parameter or read global sort
- Feature folder structure: `features/home/`, `features/tasks/` — sort logic may live in a shared location or in each feature
### Integration Points
- `HomeScreen` AppBar — add dropdown to trailing actions
- `TaskListScreen` AppBar — already has edit/delete actions; add dropdown alongside
- `CalendarDao.watchTasksForDate()` — currently sorts alphabetically; needs sort-aware query or in-memory sort
- `TasksDao.watchTasksInRoom()` — currently sorts by nextDueDate; needs sort-aware query or in-memory sort
- `SharedPreferences` — not yet used in the app; needs package addition and provider setup
- `app_de.arb` — add localization strings for sort labels
</code_context>
<deferred>
## Deferred Ideas
None — discussion stayed within phase scope
</deferred>
---
*Phase: 07-task-sorting*
*Context gathered: 2026-03-16*

View File

@@ -0,0 +1,135 @@
---
phase: 07-task-sorting
verified: 2026-03-16T22:00:00Z
status: passed
score: 9/9 must-haves verified
re_verification: false
---
# Phase 7: Task Sorting Verification Report
**Phase Goal:** Users can reorder task lists by the dimension most useful to them — name, how often the task recurs, or how much effort it requires
**Verified:** 2026-03-16T22:00:00Z
**Status:** passed
**Re-verification:** No — initial verification
---
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|----|--------------------------------------------------------------------------------|------------|------------------------------------------------------------------------------------------------------|
| 1 | Sort preference persists across app restarts | VERIFIED | `SortPreferenceNotifier._loadPersisted()` reads `SharedPreferences.getString('task_sort_option')` on build; 2 restart-recovery tests pass |
| 2 | CalendarDayList tasks are sorted according to the active sort preference | VERIFIED | `calendarDayProvider` calls `ref.watch(sortPreferenceProvider)` and applies `_sortTasks(dayTasks, sortOption)` before returning `CalendarDayState` |
| 3 | TaskListScreen tasks are sorted according to the active sort preference | VERIFIED | `tasksInRoomProvider` calls `ref.watch(sortPreferenceProvider)` and applies `stream.map((tasks) => _sortTasksRaw(tasks, sortOption))` |
| 4 | Default sort is alphabetical (matches current CalendarDayList behavior) | VERIFIED | `SortPreferenceNotifier.build()` returns `TaskSortOption.alphabetical` synchronously; test "build() returns default state of alphabetical" confirms |
| 5 | A sort dropdown is visible in the HomeScreen AppBar showing the current label | VERIFIED | `HomeScreen.build()` returns `Scaffold(appBar: AppBar(actions: const [SortDropdown()]))` — wired and rendered |
| 6 | A sort dropdown is visible in the TaskListScreen AppBar | VERIFIED | `TaskListScreen.build()` AppBar actions list: `[const SortDropdown(), edit IconButton, delete IconButton]` |
| 7 | Tapping the dropdown shows three options: A-Z, Intervall, Aufwand | VERIFIED | `SortDropdown` builds `PopupMenuButton` from `TaskSortOption.values` (3 items), labels map to `l10n.sortAlphabetical/sortInterval/sortEffort` |
| 8 | Selecting a sort option updates the task list order immediately | VERIFIED | `onSelected` calls `ref.read(sortPreferenceProvider.notifier).setSortOption(value)`; providers watch `sortPreferenceProvider` and rebuild reactively |
| 9 | The sort preference persists across screen navigations and app restarts | VERIFIED | `@Riverpod(keepAlive: true)` prevents disposal during navigation; SharedPreferences stores and reloads value |
**Score:** 9/9 truths verified
---
## Required Artifacts
### Plan 07-01 Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `lib/features/tasks/domain/task_sort_option.dart` | `TaskSortOption` enum with alphabetical, interval, effort | VERIFIED | Exactly 3 values, comments match intent. No stubs. |
| `lib/features/tasks/presentation/sort_preference_notifier.dart` | `SortPreferenceNotifier` with SharedPreferences persistence | VERIFIED | `build()` returns `alphabetical` synchronously, `_loadPersisted()` async, `setSortOption()` sets state + persists. Pattern matches `ThemeNotifier`. |
| `lib/features/tasks/presentation/sort_preference_notifier.g.dart` | Generated Riverpod provider file | VERIFIED | Generated correctly; `sortPreferenceProvider` declared as `SortPreferenceNotifierProvider._()` with `isAutoDispose: false` (keepAlive). |
| `lib/features/home/presentation/calendar_providers.dart` | `calendarDayProvider` sorts `dayTasks` by active sort preference | VERIFIED | `ref.watch(sortPreferenceProvider)` present. `_sortTasks()` helper implements all 3 sort modes. `overdueTasks` intentionally unsorted. |
| `lib/features/tasks/presentation/task_providers.dart` | `tasksInRoomProvider` sorts tasks by active sort preference | VERIFIED | `ref.watch(sortPreferenceProvider)` present. `_sortTasksRaw()` helper + `stream.map()` applied correctly. |
| `test/features/tasks/presentation/sort_preference_notifier_test.dart` | Unit tests for sort preference persistence and default | VERIFIED | 7 tests: default alphabetical, setSortOption interval, setSortOption effort, persist to SharedPreferences, restart recovery (effort), restart recovery (interval), unknown value fallback. |
### Plan 07-02 Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `lib/features/tasks/presentation/sort_dropdown.dart` | Reusable `SortDropdown` `ConsumerWidget` | VERIFIED | `ConsumerWidget`, `PopupMenuButton<TaskSortOption>`, Opacity check mark pattern, `ref.watch` for display, `ref.read` for mutation, `_label()` helper. |
| `lib/features/home/presentation/home_screen.dart` | HomeScreen with AppBar containing `SortDropdown` | VERIFIED | `Scaffold(appBar: AppBar(title: Text(l10n.tabHome), actions: const [SortDropdown()]))`. Existing Stack body preserved. |
| `lib/features/tasks/presentation/task_list_screen.dart` | TaskListScreen AppBar with `SortDropdown` before edit/delete | VERIFIED | `actions: [const SortDropdown(), IconButton(edit), IconButton(delete)]`. Correct order. |
---
## Key Link Verification
### Plan 07-01 Key Links
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `calendar_providers.dart` | `sortPreferenceProvider` | `ref.watch(sortPreferenceProvider)` in `calendarDayProvider` | WIRED | Line 77: `final sortOption = ref.watch(sortPreferenceProvider);`. Applied at line 101: `dayTasks: _sortTasks(dayTasks, sortOption)`. |
| `task_providers.dart` | `sortPreferenceProvider` | `ref.watch(sortPreferenceProvider)` in `tasksInRoomProvider` | WIRED | Line 43: `final sortOption = ref.watch(sortPreferenceProvider);`. Applied at lines 44-46 via `stream.map`. |
### Plan 07-02 Key Links
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `sort_dropdown.dart` | `sortPreferenceProvider` | `ref.watch` for display, `ref.read` for mutation | WIRED | Line 21: `ref.watch(sortPreferenceProvider)`. Line 27: `ref.read(sortPreferenceProvider.notifier).setSortOption(value)`. |
| `home_screen.dart` | `sort_dropdown.dart` | `SortDropdown` widget in AppBar actions | WIRED | Import on line 7. Used in `AppBar(actions: const [SortDropdown()])` on line 37. |
| `task_list_screen.dart` | `sort_dropdown.dart` | `SortDropdown` widget in AppBar actions | WIRED | Import on line 7. Used in `actions: [const SortDropdown(), ...]` on line 31. |
---
## Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-------------|--------|----------|
| SORT-01 | 07-01, 07-02 | User can sort tasks alphabetically | SATISFIED | `TaskSortOption.alphabetical` is the default. `_sortTasks()` and `_sortTasksRaw()` implement case-insensitive A-Z sort. `SortDropdown` displays "AZ" label (l10n). |
| SORT-02 | 07-01, 07-02 | User can sort tasks by frequency interval | SATISFIED | `TaskSortOption.interval` sort implemented: `intervalType.index` ascending with `intervalDays` tiebreaker. Displayed as "Intervall" in `SortDropdown`. |
| SORT-03 | 07-01, 07-02 | User can sort tasks by effort level | SATISFIED | `TaskSortOption.effort` sort implemented: `effortLevel.index` ascending (low=0, medium=1, high=2). Displayed as "Aufwand" in `SortDropdown`. |
No orphaned requirements. All three SORT requirements are claimed by both plans and fully implemented.
---
## Anti-Patterns Found
None. All seven implementation files scanned — no TODO, FIXME, XXX, HACK, PLACEHOLDER, return null, return {}, return [], or empty arrow functions found.
---
## Human Verification Required
### 1. Visual check: Sort dropdown appearance in AppBar
**Test:** Launch the app, navigate to HomeScreen. Verify the AppBar shows a sort icon (Icons.sort) followed by the current sort label text "AZ".
**Expected:** Sort icon and "AZ" text visible in the top-right AppBar area.
**Why human:** Widget rendering and visual layout cannot be verified programmatically.
### 2. Popup menu interaction: Check marks on active option
**Test:** Tap the sort dropdown, verify three items appear with a check mark next to the currently selected option and no check mark on the other two.
**Expected:** Check mark visible on "AZ" (default), invisible (but space-preserving) on "Intervall" and "Aufwand".
**Why human:** Opacity(0) vs Opacity(1) rendering and visual alignment cannot be verified with grep.
### 3. Reactive reorder on selection
**Test:** With tasks loaded in HomeScreen, tap the sort dropdown and select "Aufwand". Verify the task list reorders immediately without a page reload.
**Expected:** Task list updates instantly, sorted low-effort first.
**Why human:** Real-time Riverpod reactive rebuild requires a running app to observe.
### 4. Cross-screen persistence of sort preference
**Test:** Select "Intervall" in HomeScreen, then navigate to a room's TaskListScreen. Verify the sort dropdown there also shows "Intervall".
**Expected:** Sort preference is shared across screens (same `sortPreferenceProvider`, `keepAlive: true`).
**Why human:** Cross-screen navigation state cannot be verified statically.
---
## Gaps Summary
None. All 9 observable truths verified. All artifacts exist, are substantive, and are correctly wired. All 3 requirements (SORT-01, SORT-02, SORT-03) are fully satisfied. All 5 commits (a9f2983, 13c7d62, 3697e4e, e5eccb7, a3e4d02) confirmed present in git log. No anti-patterns detected in implementation files.
The phase delivers its stated goal: users can reorder task lists by name (AZ), frequency interval, or effort level via a persistent, reactive sort preference accessible from both HomeScreen and TaskListScreen AppBars.
---
_Verified: 2026-03-16T22:00:00Z_
_Verifier: Claude (gsd-verifier)_

View File

@@ -0,0 +1,310 @@
---
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>

View File

@@ -0,0 +1,152 @@
---
phase: 08-task-delete
plan: 01
subsystem: database
tags: [drift, sqlite, flutter, soft-delete, schema-migration]
# Dependency graph
requires: []
provides:
- isActive BoolColumn on Tasks table (schema v3)
- softDeleteTask(taskId) method on TasksDao
- getCompletionCount(taskId) method on TasksDao
- isActive=true filter on all task queries across all 4 DAOs
- Schema migration from v2 to v3 (addColumn)
affects: [08-02, 08-03, delete-dialog, task-providers]
# Tech tracking
tech-stack:
added: []
patterns:
- "Soft delete via isActive BoolColumn with default true"
- "All task-returning DAO queries filter by isActive=true"
- "Schema versioning via Drift addColumn migration"
- "TDD: RED commit before implementation GREEN commit"
key-files:
created:
- drift_schemas/household_keeper/drift_schema_v3.json
- test/drift/household_keeper/generated/schema_v3.dart
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
- test/drift/household_keeper/migration_test.dart
- test/drift/household_keeper/generated/schema.dart
- test/core/database/database_test.dart
- test/features/home/presentation/home_screen_test.dart
- test/features/tasks/presentation/task_list_screen_test.dart
key-decisions:
- "isActive column uses BoolColumn.withDefault(true) so existing rows are automatically active after migration"
- "Migration uses from==2 (not from<3) for addColumn to avoid duplicate-column error when upgrading from v1 (where createTable already includes isActive)"
- "Migration tests updated to only test paths ending at v3 (current schemaVersion) since AppDatabase always migrates to its schemaVersion"
patterns-established:
- "Soft-delete pattern: isActive BoolColumn with default true, filter all queries by isActive=true"
- "Hard-delete remains via deleteTask(id) which cascades to completions"
requirements-completed: [DEL-02, DEL-03]
# Metrics
duration: 9min
completed: 2026-03-18
---
# Phase 8 Plan 01: isActive Column and DAO Filtering Summary
**Drift schema v3 with isActive BoolColumn on Tasks, soft-delete DAO methods, and isActive=true filter applied to all 15 task queries across TasksDao, CalendarDao, DailyPlanDao, and RoomsDao**
## Performance
- **Duration:** 9 min
- **Started:** 2026-03-18T19:47:32Z
- **Completed:** 2026-03-18T19:56:39Z
- **Tasks:** 2
- **Files modified:** 11
## Accomplishments
- Added `isActive BoolColumn` (default `true`) to Tasks table with schema v3 migration
- Added `softDeleteTask(taskId)` and `getCompletionCount(taskId)` to TasksDao
- Applied `isActive=true` filter to all task-returning queries across all 4 DAOs (15 total query sites)
- 6 new tests passing (softDeleteTask, getCompletionCount, watchTasksInRoom filtering, getOverdueTaskCount filtering, hard deleteTask still works)
- All 144 tests pass, dart analyze reports zero issues
## Task Commits
Each task was committed atomically:
1. **TDD RED: Failing tests** - `a2cef91` (test)
2. **Task 1: Add isActive column, migration v3, softDeleteTask and getCompletionCount** - `4b51f5f` (feat)
3. **Task 2: Add isActive filters to CalendarDao, DailyPlanDao, RoomsDao** - `b2f14dc` (feat)
## Files Created/Modified
- `lib/core/database/database.dart` - Added isActive BoolColumn to Tasks, bumped schemaVersion to 3, added from==2 migration
- `lib/features/tasks/data/tasks_dao.dart` - Added isActive filter to watchTasksInRoom and getOverdueTaskCount, added softDeleteTask and getCompletionCount methods
- `lib/features/home/data/calendar_dao.dart` - Added isActive=true filter to all 6 query methods
- `lib/features/home/data/daily_plan_dao.dart` - Added isActive=true filter to all 3 query methods
- `lib/features/rooms/data/rooms_dao.dart` - Added isActive=true filter to watchRoomWithStats task query
- `test/features/tasks/data/tasks_dao_test.dart` - Added 6 new tests for soft-delete behavior
- `test/drift/household_keeper/migration_test.dart` - Updated to test v1→v3 and v2→v3 migrations
- `test/drift/household_keeper/generated/schema_v3.dart` - Generated schema snapshot for v3
- `test/drift/household_keeper/generated/schema.dart` - Updated to include v3 in versions list
- `drift_schemas/household_keeper/drift_schema_v3.json` - v3 schema JSON for Drift migration tooling
- `test/core/database/database_test.dart` - Updated schemaVersion assertion from 2 to 3
- `test/features/home/presentation/home_screen_test.dart` - Added isActive: true to Task constructor helper
- `test/features/tasks/presentation/task_list_screen_test.dart` - Added isActive: true to Task constructor helper
## Decisions Made
- `isActive` column uses `BoolColumn.withDefault(const Constant(true))` so all existing rows become active after migration without explicit data backfill
- Migration uses `from == 2` (not `from < 3`) for `addColumn` to avoid duplicate-column error when upgrading from v1 where `createTable` already includes the isActive column in the current schema definition
- Migration test framework updated to only test paths that end at the current schema version (v3), since `AppDatabase.schemaVersion = 3` means all migrations go to v3
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Fixed Task constructor calls missing isActive parameter in test helpers**
- **Found during:** Task 2 (running full test suite)
- **Issue:** After adding `isActive` as a required field on the generated `Task` dataclass, two test files with manual `Task(...)` constructors (`home_screen_test.dart`, `task_list_screen_test.dart`) failed to compile
- **Fix:** Added `isActive: true` to `_makeTask` helper functions in both files
- **Files modified:** `test/features/home/presentation/home_screen_test.dart`, `test/features/tasks/presentation/task_list_screen_test.dart`
- **Verification:** flutter test passes, all 144 tests pass
- **Committed in:** `b2f14dc` (Task 2 commit)
**2. [Rule 1 - Bug] Fixed schemaVersion assertion in database_test.dart**
- **Found during:** Task 2 (running full test suite)
- **Issue:** `database_test.dart` had `expect(db.schemaVersion, equals(2))` which failed after bumping to v3
- **Fix:** Updated assertion to `equals(3)` and renamed test to "has schemaVersion 3"
- **Files modified:** `test/core/database/database_test.dart`
- **Verification:** Test passes
- **Committed in:** `b2f14dc` (Task 2 commit)
**3. [Rule 1 - Bug] Fixed Drift migration tests for v3 schema**
- **Found during:** Task 2 (running full test suite)
- **Issue:** Migration tests tested v1→v2 migration, but AppDatabase.schemaVersion=3 causes all migrations to end at v3. Also, the `from < 3` addColumn migration caused a duplicate-column error when migrating from v1 (since createTable already includes isActive)
- **Fix:** (a) Generated schema_v3.dart snapshot, (b) Updated migration_test.dart to test v1→v3 and v2→v3, (c) Changed migration to `from == 2` instead of `from < 3`
- **Files modified:** `test/drift/household_keeper/migration_test.dart`, `test/drift/household_keeper/generated/schema_v3.dart`, `test/drift/household_keeper/generated/schema.dart`, `drift_schemas/household_keeper/drift_schema_v3.json`
- **Verification:** All 3 migration tests pass
- **Committed in:** `b2f14dc` (Task 2 commit)
---
**Total deviations:** 3 auto-fixed (all Rule 1 bugs caused directly by schema change)
**Impact on plan:** All fixes necessary for correctness. No scope creep.
## Issues Encountered
- The Drift migration testing framework requires schema snapshots for each version. Adding schema v3 required regenerating schema files and fixing the migration test to only test paths to the current version.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- `isActive` column and `softDeleteTask`/`getCompletionCount` methods are ready for use by the UI layer (task delete dialog in plan 08-02)
- All active views (calendar, room task list, daily plan, room stats) now correctly exclude soft-deleted tasks
- Hard delete (deleteTask) remains unchanged and still cascades to completions
---
*Phase: 08-task-delete*
*Completed: 2026-03-18*

View File

@@ -0,0 +1,290 @@
---
phase: 08-task-delete
plan: 02
type: execute
wave: 2
depends_on: ["08-01"]
files_modified:
- lib/features/tasks/presentation/task_providers.dart
- lib/features/tasks/presentation/task_form_screen.dart
autonomous: true
requirements: [DEL-01, DEL-04]
must_haves:
truths:
- "User sees a red delete button at the bottom of the task edit form"
- "Tapping delete shows a confirmation dialog before any action"
- "Confirming delete on a task with no completions removes it from the database"
- "Confirming delete on a task with completions deactivates it (hidden from views)"
- "After deletion the user is navigated back to the room task list"
artifacts:
- path: "lib/features/tasks/presentation/task_form_screen.dart"
provides: "Delete button and confirmation dialog in edit mode"
contains: "taskDeleteConfirmTitle"
- path: "lib/features/tasks/presentation/task_providers.dart"
provides: "Smart delete method using getCompletionCount"
contains: "softDeleteTask"
key_links:
- from: "lib/features/tasks/presentation/task_form_screen.dart"
to: "lib/features/tasks/presentation/task_providers.dart"
via: "TaskActions.smartDeleteTask call from delete button callback"
pattern: "smartDeleteTask"
- from: "lib/features/tasks/presentation/task_providers.dart"
to: "lib/features/tasks/data/tasks_dao.dart"
via: "getCompletionCount + conditional deleteTask or softDeleteTask"
pattern: "getCompletionCount.*softDeleteTask|deleteTask"
---
<objective>
Add the delete button and confirmation dialog to the task edit form, with smart delete logic in the provider layer.
Purpose: Users can remove tasks they no longer need. The smart behavior (hard vs soft delete) is invisible to the user -- they just see "delete" with a confirmation.
Output: Working delete flow on the task edit form: red button -> confirmation dialog -> smart delete -> navigate back.
</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
@.planning/phases/08-task-delete/08-01-SUMMARY.md
<interfaces>
<!-- Key types and contracts the executor needs. -->
From lib/features/tasks/presentation/task_providers.dart (existing TaskActions):
```dart
@riverpod
class TaskActions extends _$TaskActions {
@override
FutureOr<void> build() {}
Future<int> createTask({...}) async { ... }
Future<void> updateTask(Task task) async { ... }
Future<void> deleteTask(int taskId) async { ... } // calls DAO hard delete
Future<void> completeTask(int taskId) async { ... }
}
```
From lib/features/tasks/data/tasks_dao.dart (after Plan 01):
```dart
class TasksDao {
Future<void> deleteTask(int taskId); // hard delete (cascade)
Future<void> softDeleteTask(int taskId); // sets isActive = false
Future<int> getCompletionCount(int taskId); // count completions
}
```
From lib/features/tasks/presentation/task_form_screen.dart (edit mode section):
```dart
// History section (edit mode only) — delete button goes AFTER this
if (widget.isEditing) ...[
const SizedBox(height: 24),
const Divider(),
ListTile(
leading: const Icon(Icons.history),
title: Text(l10n.taskHistoryTitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => showTaskHistorySheet(
context: context,
taskId: widget.taskId!,
),
),
],
```
From lib/l10n/app_de.arb (existing delete l10n strings):
```json
"taskDeleteConfirmTitle": "Aufgabe l\u00f6schen?",
"taskDeleteConfirmMessage": "Die Aufgabe wird unwiderruflich gel\u00f6scht.",
"taskDeleteConfirmAction": "L\u00f6schen"
```
Room delete dialog pattern (from lib/features/rooms/presentation/rooms_screen.dart:165-189):
```dart
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(l10n.roomDeleteConfirmTitle),
content: Text(l10n.roomDeleteConfirmMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: Text(l10n.cancel),
),
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: Theme.of(ctx).colorScheme.error,
),
onPressed: () { ... },
child: Text(l10n.roomDeleteConfirmAction),
),
],
),
);
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add smartDeleteTask to TaskActions provider</name>
<files>lib/features/tasks/presentation/task_providers.dart</files>
<action>
Add a `smartDeleteTask` method to the `TaskActions` class in task_providers.dart. This method checks the completion count and routes to hard delete or soft delete accordingly:
```dart
/// Smart delete: hard-deletes tasks with no completions, soft-deletes tasks with completions.
Future<void> smartDeleteTask(int taskId) async {
final db = ref.read(appDatabaseProvider);
final completionCount = await db.tasksDao.getCompletionCount(taskId);
if (completionCount == 0) {
await db.tasksDao.deleteTask(taskId);
} else {
await db.tasksDao.softDeleteTask(taskId);
}
}
```
Keep the existing `deleteTask` method unchanged (it is still a valid hard delete for other uses like room cascade delete).
Run `dart run build_runner build --delete-conflicting-outputs` to regenerate the provider code.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze --fatal-infos lib/features/tasks/presentation/task_providers.dart</automated>
</verify>
<done>
- smartDeleteTask method exists on TaskActions
- Method checks completion count and routes to hard or soft delete
- dart analyze passes with zero issues
</done>
</task>
<task type="auto">
<name>Task 2: Add delete button and confirmation dialog to TaskFormScreen</name>
<files>lib/features/tasks/presentation/task_form_screen.dart</files>
<action>
1. In the TaskFormScreen build method's ListView children, AFTER the history section (the existing `if (widget.isEditing) ...` block ending at line ~204), add the delete button section inside the same `if (widget.isEditing)` block:
```dart
if (widget.isEditing) ...[
const SizedBox(height: 24),
const Divider(),
// History ListTile (existing)
ListTile(
leading: const Icon(Icons.history),
title: Text(l10n.taskHistoryTitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => showTaskHistorySheet(
context: context,
taskId: widget.taskId!,
),
),
// DELETE BUTTON — new
const Divider(),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
style: FilledButton.styleFrom(
backgroundColor: theme.colorScheme.error,
foregroundColor: theme.colorScheme.onError,
),
onPressed: _isLoading ? null : _onDelete,
icon: const Icon(Icons.delete_outline),
label: Text(l10n.taskDeleteConfirmAction),
),
),
],
```
2. Add a `_onDelete` method to _TaskFormScreenState:
```dart
Future<void> _onDelete() async {
final l10n = AppLocalizations.of(context);
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(l10n.taskDeleteConfirmTitle),
content: Text(l10n.taskDeleteConfirmMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text(l10n.cancel),
),
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: Theme.of(ctx).colorScheme.error,
),
onPressed: () => Navigator.pop(ctx, true),
child: Text(l10n.taskDeleteConfirmAction),
),
],
),
);
if (confirmed != true || !mounted) return;
setState(() => _isLoading = true);
try {
await ref.read(taskActionsProvider.notifier).smartDeleteTask(widget.taskId!);
if (mounted) {
context.pop();
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
```
Note: The `l10n.cancel` string should already exist from the room delete dialog. If not, use `MaterialLocalizations.of(context).cancelButtonLabel`.
3. Verify `cancel` l10n key exists. If it does not exist in app_de.arb, check for the existing cancel button pattern in rooms_screen.dart and use the same approach.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter test --reporter compact && dart analyze --fatal-infos</automated>
</verify>
<done>
- Red delete button visible at bottom of task edit form (below history, separated by divider)
- Delete button only shows in edit mode (not create mode)
- Tapping delete shows AlertDialog with title "Aufgabe loschen?" and error-colored confirm button
- Canceling dialog does nothing
- Confirming dialog calls smartDeleteTask and pops back to room task list
- Button is disabled while loading (_isLoading)
- All existing tests pass, dart analyze clean
</done>
</task>
</tasks>
<verification>
- Task edit form shows red delete button below history section with divider separator
- Delete button is NOT shown in create mode
- Tapping delete shows confirmation dialog matching room delete dialog pattern
- Confirming deletes/deactivates the task and navigates back
- Canceling returns to the form without changes
- All tests pass: `flutter test --reporter compact`
- Code quality: `dart analyze --fatal-infos` reports zero issues
</verification>
<success_criteria>
- Complete delete flow works: open task -> scroll to bottom -> tap delete -> confirm -> back to room task list
- Smart delete is invisible to user: tasks with completions are deactivated, tasks without are removed
- Delete button follows Material 3 error color pattern
- Confirmation dialog uses existing German l10n strings
- All 137+ tests pass
</success_criteria>
<output>
After completion, create `.planning/phases/08-task-delete/08-02-SUMMARY.md`
</output>

View File

@@ -0,0 +1,117 @@
---
phase: 08-task-delete
plan: 02
subsystem: ui
tags: [flutter, riverpod, drift, material3, l10n]
# Dependency graph
requires:
- phase: 08-task-delete plan 01
provides: softDeleteTask and getCompletionCount on TasksDao, isActive column migration
provides:
- smartDeleteTask on TaskActions provider (hard delete if 0 completions, soft delete otherwise)
- Red delete button in task edit form with Material 3 error color
- Confirmation AlertDialog using existing German l10n strings
- Full delete flow: button -> dialog -> smart delete -> navigate back
affects: [task-form, task-providers, any future task management UI]
# Tech tracking
tech-stack:
added: []
patterns:
- Smart delete pattern: check completion count to decide hard vs soft delete
- Delete confirmation dialog matching room delete pattern with error-colored FilledButton
key-files:
created: []
modified:
- lib/features/tasks/presentation/task_providers.dart
- lib/features/tasks/presentation/task_form_screen.dart
key-decisions:
- "smartDeleteTask kept separate from deleteTask to preserve existing hard-delete path for cascade/other uses"
- "Delete button placed after history section with divider, visible only in edit mode"
patterns-established:
- "Delete confirmation pattern: showDialog<bool> returning true/false, error-colored FilledButton for confirm"
- "Smart delete pattern: getCompletionCount -> conditional hard/soft delete invisible to user"
requirements-completed: [DEL-01, DEL-04]
# Metrics
duration: 2min
completed: 2026-03-18
---
# Phase 8 Plan 02: Task Delete UI Summary
**Red delete button with confirmation dialog in task edit form: hard-deletes unused tasks, soft-deletes tasks with completion history**
## Performance
- **Duration:** 2 min
- **Started:** 2026-03-18T19:59:37Z
- **Completed:** 2026-03-18T20:02:05Z
- **Tasks:** 2
- **Files modified:** 2
## Accomplishments
- Added `smartDeleteTask` to `TaskActions` Riverpod notifier — checks completion count and routes to DAO `deleteTask` (hard) or `softDeleteTask` (soft)
- Added red `FilledButton.icon` with error colorScheme at bottom of task edit form, separated from history section by a `Divider`
- Added `_onDelete` confirmation dialog using existing `taskDeleteConfirmTitle`, `taskDeleteConfirmMessage`, `taskDeleteConfirmAction`, and `cancel` l10n strings
- All 144 tests pass, `dart analyze --fatal-infos` clean
## Task Commits
Each task was committed atomically:
1. **Task 1: Add smartDeleteTask to TaskActions provider** - `1b1b981` (feat)
2. **Task 2: Add delete button and confirmation dialog to TaskFormScreen** - `6133c97` (feat)
**Plan metadata:** _(docs commit follows)_
## Files Created/Modified
- `lib/features/tasks/presentation/task_providers.dart` - Added `smartDeleteTask` method to `TaskActions` class
- `lib/features/tasks/presentation/task_providers.g.dart` - Regenerated by build_runner (no functional changes)
- `lib/features/tasks/presentation/task_form_screen.dart` - Added delete button in edit mode and `_onDelete` async method
## Decisions Made
- `smartDeleteTask` kept separate from `deleteTask` to preserve the existing hard-delete path used for room cascade deletes and any other callers
- Delete button placed after history ListTile, inside the `if (widget.isEditing)` block, so it never appears in create mode
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Complete delete flow is working: task edit form -> red delete button -> confirmation dialog -> smart delete -> pop back to room task list
- Soft-deleted tasks (isActive=false) are already filtered from all views (implemented in Plan 01)
- Phase 08-task-delete is fully complete
---
*Phase: 08-task-delete*
*Completed: 2026-03-18*
## Self-Check: PASSED
- FOUND: lib/features/tasks/presentation/task_providers.dart
- FOUND: lib/features/tasks/presentation/task_form_screen.dart
- FOUND: .planning/phases/08-task-delete/08-02-SUMMARY.md
- FOUND: commit 1b1b981 (smartDeleteTask)
- FOUND: commit 6133c97 (delete button and dialog)
- FOUND: smartDeleteTask in task_providers.dart
- FOUND: taskDeleteConfirmTitle in task_form_screen.dart

View File

@@ -0,0 +1,94 @@
# Phase 8: Task Delete - Context
**Gathered:** 2026-03-18
**Status:** Ready for planning
<domain>
## Phase Boundary
Add a delete action to tasks with smart behavior: hard delete (remove from DB) if the task has never been completed, soft delete (deactivate, hide from views) if the task has been completed at least once. Preserves completion history for future statistics. No UI to view or restore archived tasks in this phase.
</domain>
<decisions>
## Implementation Decisions
### Delete placement
- Red delete button at the bottom of the task edit form, below the history section, separated by a divider
- Edit mode only — no delete button in create mode (user can just back out)
- No swipe-to-delete, no long-press context menu, no AppBar icon — form only
- Deliberate action: user must open the task, scroll down, and tap delete
### Confirmation dialog
- Single generic "Aufgabe löschen?" confirmation — same message for both hard and soft delete
- User does not need to know the implementation difference (permanent vs deactivated)
- Follow existing room delete dialog pattern: TextButton cancel + FilledButton with error color
- Existing l10n strings (taskDeleteConfirmTitle, taskDeleteConfirmMessage, taskDeleteConfirmAction) already defined
### Delete behavior
- Check task_completions count before deleting
- 0 completions → hard delete: remove task and completions from DB (existing deleteTask DAO method)
- 1+ completions → soft delete: set isActive = false on the task, task hidden from all active views
- Need new `isActive` BoolColumn on Tasks table with default true + schema migration
### Post-delete navigation
- Pop back to the room task list (same as save behavior)
- Reactive providers will auto-update to reflect the deleted/deactivated task
### Archived task visibility
- Soft-deleted tasks are completely hidden from all views — no toggle, no restore UI
- Archived tasks preserved in DB purely for future statistics phase
- No need to build any "show archived" UI in this phase
### Claude's Discretion
- Migration version number and strategy
- Exact button styling (consistent with Material 3 error patterns)
- Whether to add a SnackBar confirmation after delete or just navigate back silently
</decisions>
<specifics>
## Specific Ideas
- User explicitly wants simplicity: "just have a delete function keep it simple"
- The smart hard/soft behavior is invisible to the user — they just see "delete"
- Keep the flow dead simple: open task → scroll to bottom → tap delete → confirm → back to list
</specifics>
<code_context>
## Existing Code Insights
### Reusable Assets
- `TasksDao.deleteTask(taskId)`: Already implements hard delete with cascade (completions first, then task)
- `TaskActionsNotifier.deleteTask(taskId)`: Provider method exists, calls DAO
- Room delete confirmation dialog (`rooms_screen.dart:160-189`): AlertDialog pattern with error-colored FilledButton
- German l10n strings already defined: taskDeleteConfirmTitle, taskDeleteConfirmMessage, taskDeleteConfirmAction
### Established Patterns
- Confirmation dialogs: AlertDialog with TextButton cancel + error FilledButton
- DAO transactions for cascade deletes
- ConsumerStatefulWidget for screens with async callbacks
- Schema migrations in database.dart with version tracking
### Integration Points
- `task_form_screen.dart`: Add delete button after history ListTile (edit mode only)
- `tasks_dao.dart`: Add softDeleteTask method (UPDATE isActive = false) alongside existing hard deleteTask
- `calendar_dao.dart`: 6 queries need WHERE isActive = true filter
- `tasks_dao.dart`: watchTasksInRoom needs WHERE isActive = true filter
- `database.dart`: Add isActive BoolColumn to Tasks table + migration
- All existing tasks must default to isActive = true in migration
</code_context>
<deferred>
## Deferred Ideas
None — discussion stayed within phase scope
</deferred>
---
*Phase: 08-task-delete*
*Context gathered: 2026-03-18*

View File

@@ -0,0 +1,124 @@
---
phase: 08-task-delete
verified: 2026-03-18T20:30:00Z
status: passed
score: 9/9 must-haves verified
re_verification: false
---
# Phase 8: Task Delete Verification Report
**Phase Goal:** Users can remove tasks they no longer need, with smart preservation of completion history for future statistics
**Verified:** 2026-03-18T20:30:00Z
**Status:** passed
**Re-verification:** No — initial verification
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Active tasks appear in all views (calendar, room task lists, daily plan) | VERIFIED | All 4 DAOs filter `isActive=true`; 15 query sites confirmed across `tasks_dao.dart`, `calendar_dao.dart`, `daily_plan_dao.dart`, `rooms_dao.dart` |
| 2 | Deactivated tasks are hidden from all views | VERIFIED | `isActive.equals(true)` present on all 6 CalendarDao queries, all 3 DailyPlanDao queries, `watchTasksInRoom`, `getOverdueTaskCount`, and `watchRoomWithStats` |
| 3 | Hard delete removes task and completions from DB entirely | VERIFIED | `deleteTask` in `tasks_dao.dart` uses a transaction to delete completions then task; test "hard deleteTask still removes task and its completions" passes |
| 4 | Soft delete sets isActive to false without removing data | VERIFIED | `softDeleteTask` updates `isActive: Value(false)` only; test "softDeleteTask sets isActive to false without removing the task" passes — row count stays 1, `isActive == false` |
| 5 | Existing tasks default to active after migration | VERIFIED | `BoolColumn.withDefault(const Constant(true))` on Tasks table; `from == 2` migration block calls `m.addColumn(tasks, tasks.isActive)` which applies the default to existing rows |
| 6 | User sees a red delete button at the bottom of the task edit form | VERIFIED | `FilledButton.icon` with `backgroundColor: theme.colorScheme.error` inside `if (widget.isEditing)` block in `task_form_screen.dart` lines 207-218 |
| 7 | Tapping delete shows a confirmation dialog before any action | VERIFIED | `_onDelete()` calls `showDialog<bool>` with `AlertDialog` containing title `l10n.taskDeleteConfirmTitle`, message `l10n.taskDeleteConfirmMessage`, cancel TextButton, and error-colored confirm FilledButton |
| 8 | Confirming delete routes to hard or soft delete based on completion history | VERIFIED | `smartDeleteTask` in `task_providers.dart` calls `getCompletionCount` and branches: `deleteTask` if 0, `softDeleteTask` if >0 |
| 9 | After deletion the user is navigated back to the room task list | VERIFIED | `_onDelete()` calls `context.pop()` after awaiting `smartDeleteTask`; guarded by `if (mounted)` check |
**Score:** 9/9 truths verified
---
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `lib/core/database/database.dart` | `isActive` BoolColumn, schema v3, migration | VERIFIED | Line 38-39: `BoolColumn get isActive => boolean().withDefault(const Constant(true))();`; `schemaVersion => 3`; `from == 2` block calls `m.addColumn(tasks, tasks.isActive)` |
| `lib/features/tasks/data/tasks_dao.dart` | `softDeleteTask`, `getCompletionCount`, `isActive` filter on queries | VERIFIED | Both methods present (lines 115-128); `watchTasksInRoom` and `getOverdueTaskCount` filter by `isActive.equals(true)` |
| `lib/features/home/data/calendar_dao.dart` | `isActive=true` filter on all 6 queries | VERIFIED | All 6 methods confirmed: `watchTasksForDate` (l32), `getTaskCount` (l56), `watchTasksForDateInRoom` (l76), `watchOverdueTasks` (l105), `watchOverdueTasksInRoom` (l139), `getTaskCountInRoom` (l164) |
| `lib/features/home/data/daily_plan_dao.dart` | `isActive=true` filter on all 3 queries | VERIFIED | `watchAllTasksWithRoomName` (l20), `getOverdueAndTodayTaskCount` (l44), `getOverdueTaskCount` (l57) — all confirmed |
| `lib/features/rooms/data/rooms_dao.dart` | `isActive=true` filter in `watchRoomWithStats` task query | VERIFIED | Line 47-49: `t.roomId.equals(room.id) & t.isActive.equals(true)` |
| `lib/features/tasks/presentation/task_providers.dart` | `smartDeleteTask` method using `getCompletionCount` | VERIFIED | Lines 94-102: method exists, branches on completion count to `deleteTask` or `softDeleteTask` |
| `lib/features/tasks/presentation/task_form_screen.dart` | Delete button and confirmation dialog in edit mode; uses `taskDeleteConfirmTitle` | VERIFIED | Lines 204-218 (button), lines 471-507 (`_onDelete` method); `taskDeleteConfirmTitle` at line 476 |
| `test/features/tasks/data/tasks_dao_test.dart` | Tests for `softDeleteTask`, `getCompletionCount`, `isActive` filtering | VERIFIED | 6 new tests present and all 13 tests in file pass |
---
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `lib/core/database/database.dart` | All DAOs | `BoolColumn get isActive` on Tasks table | WIRED | 59 occurrences of `isActive` across 6 DAO/schema files |
| `lib/features/tasks/data/tasks_dao.dart` | `lib/features/tasks/presentation/task_providers.dart` | `softDeleteTask` and `getCompletionCount` | WIRED | `task_providers.dart` calls `db.tasksDao.getCompletionCount(taskId)` and `db.tasksDao.softDeleteTask(taskId)` at lines 96-100 |
| `lib/features/tasks/presentation/task_form_screen.dart` | `lib/features/tasks/presentation/task_providers.dart` | `smartDeleteTask` call from `_onDelete` | WIRED | `ref.read(taskActionsProvider.notifier).smartDeleteTask(widget.taskId!)` at line 498 |
| `lib/features/tasks/presentation/task_providers.dart` | `lib/features/tasks/data/tasks_dao.dart` | `getCompletionCount` then conditional `deleteTask` or `softDeleteTask` | WIRED | `smartDeleteTask` method at lines 94-102 confirmed complete — reads count, branches, calls DAO |
---
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-------------|--------|----------|
| DEL-01 | 08-02-PLAN.md | User can delete a task from the task edit form via a clearly visible delete action | SATISFIED | Red `FilledButton.icon` with `Icons.delete_outline` at bottom of edit form, gated on `widget.isEditing` |
| DEL-02 | 08-01-PLAN.md | Hard delete removes task with no completions from DB entirely | SATISFIED | `deleteTask` cascade + `smartDeleteTask` routes to it when `getCompletionCount == 0` |
| DEL-03 | 08-01-PLAN.md | Deleting a task with completions deactivates it (soft delete) | SATISFIED | `softDeleteTask` sets `isActive=false`; all DAO queries filter by `isActive=true` so task disappears from views |
| DEL-04 | 08-02-PLAN.md | User sees a confirmation before deleting/deactivating | SATISFIED | `_onDelete` shows `AlertDialog` with cancel and confirm actions; action only proceeds when `confirmed == true` |
All 4 requirements from REQUIREMENTS.md Phase 8 are SATISFIED. No orphaned requirements found.
---
### Anti-Patterns Found
None. No TODOs, FIXMEs, placeholder returns, or stub implementations found in any modified files.
---
### Human Verification Required
#### 1. Delete button visual appearance
**Test:** Open the app, navigate to a room, tap any task to open the edit form, scroll to the bottom.
**Expected:** A full-width red button labeled "Loschen" (with umlaut) appears below the history row, separated by a divider. Button does not appear when creating a new task.
**Why human:** Visual layout, color rendering, and scroll behavior cannot be verified programmatically.
#### 2. Confirmation dialog flow
**Test:** Tap the delete button. Tap "Abbrechen" (cancel).
**Expected:** Dialog dismisses, form remains open, task is unchanged.
**Why human:** Dialog dismissal behavior and state preservation requires manual interaction.
#### 3. Smart delete — task with no completions (hard delete)
**Test:** Create a fresh task (never completed). Open it, tap delete, confirm.
**Expected:** Task disappears from the room list immediately. Navigated back to room task list.
**Why human:** End-to-end flow requires running app with real navigation and reactive provider updates.
#### 4. Smart delete — task with completions (soft delete)
**Test:** Complete a task at least once. Open it, tap delete, confirm.
**Expected:** Task disappears from all views (room list, calendar, daily plan). Navigation returns to room. Task remains in DB (invisible to user but present for future statistics).
**Why human:** Requires verifying absence from multiple views and confirming data is preserved in DB — combination of UI behavior and DB state inspection.
---
### Gaps Summary
No gaps. All must-haves verified.
---
## Test Results
- `flutter test test/features/tasks/data/tasks_dao_test.dart`: 13/13 passed (including all 6 new soft-delete tests)
- `flutter test --reporter compact`: 144/144 passed
- `dart analyze --fatal-infos`: No issues found
---
_Verified: 2026-03-18T20:30:00Z_
_Verifier: Claude (gsd-verifier)_

View File

@@ -0,0 +1,330 @@
---
phase: 09-task-creation-ux
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- lib/features/tasks/presentation/task_form_screen.dart
- lib/l10n/app_de.arb
- lib/l10n/app_localizations.dart
- lib/l10n/app_localizations_de.dart
autonomous: false
requirements: [TCX-01, TCX-02, TCX-03, TCX-04]
must_haves:
truths:
- "Frequency section shows 4 shortcut chips (Taeglich, Woechentlich, Alle 2 Wochen, Monatlich) above the freeform picker"
- "The freeform 'Every [N] [unit]' picker row is always visible — not hidden behind a Custom toggle"
- "Tapping a shortcut chip highlights it AND populates the picker with the corresponding values"
- "Editing the picker number or unit manually deselects any highlighted chip"
- "Any arbitrary interval (e.g., every 5 days, every 3 weeks, every 2 months) can be entered directly in the freeform picker"
- "Editing an existing daily task shows 'Taeglich' chip highlighted and picker showing 1/Tage"
- "Editing an existing quarterly task (3 months) shows no chip highlighted and picker showing 3/Monate"
- "Saving a task from the new picker produces the correct IntervalType and intervalDays values"
artifacts:
- path: "lib/features/tasks/presentation/task_form_screen.dart"
provides: "Reworked frequency picker with shortcut chips + freeform picker"
contains: "_ShortcutFrequency"
- path: "lib/l10n/app_de.arb"
provides: "German l10n strings for shortcut chip labels"
contains: "frequencyShortcutDaily"
key_links:
- from: "lib/features/tasks/presentation/task_form_screen.dart"
to: "lib/features/tasks/domain/frequency.dart"
via: "IntervalType enum and FrequencyInterval for _resolveFrequency mapping"
pattern: "IntervalType\\."
- from: "lib/features/tasks/presentation/task_form_screen.dart"
to: "lib/l10n/app_de.arb"
via: "AppLocalizations for chip labels and picker labels"
pattern: "l10n\\.frequencyShortcut"
---
<objective>
Rework the frequency picker in TaskFormScreen from a flat grid of 10 preset chips + hidden "Custom" mode into an intuitive 4 shortcut chips + always-visible freeform "Every [N] [unit]" picker.
Purpose: Users should be able to set any recurring frequency intuitively — common frequencies are one tap away, custom intervals are freeform without mode switching.
Output: Reworked `task_form_screen.dart` with simplified state management, bidirectional chip/picker sync, correct edit-mode loading, and all existing scheduling behavior preserved.
</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/09-task-creation-ux/09-CONTEXT.md
<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
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
}
class FrequencyInterval {
final IntervalType intervalType;
final int days;
const FrequencyInterval({required this.intervalType, this.days = 1});
String label() { /* German label logic */ }
static const List<FrequencyInterval> presets = [ /* 10 presets */ ];
}
```
From lib/features/tasks/presentation/task_form_screen.dart (current state to rework):
```dart
// Current state variables (lines 37-39):
FrequencyInterval? _selectedPreset;
bool _isCustomFrequency = false;
_CustomUnit _customUnit = _CustomUnit.days;
// Current methods to rework:
_buildFrequencySelector() // lines 226-277 — chip grid + conditional custom input
_buildCustomFrequencyInput() // lines 279-326 — the "Alle [N] [unit]" row (KEEP, promote to primary)
_loadExistingTask() // lines 55-101 — edit mode preset matching (rework for new chips)
_resolveFrequency() // lines 378-415 — maps to IntervalType (KEEP, simplify condition)
```
From lib/l10n/app_de.arb (existing frequency strings):
```json
"taskFormFrequencyLabel": "Wiederholung",
"taskFormFrequencyCustom": "Benutzerdefiniert", // will be unused
"taskFormFrequencyEvery": "Alle",
"taskFormFrequencyUnitDays": "Tage",
"taskFormFrequencyUnitWeeks": "Wochen",
"taskFormFrequencyUnitMonths": "Monate"
```
IMPORTANT: FrequencyInterval.presets is NOT used outside of task_form_screen.dart for selection purposes.
template_picker_sheet.dart and task_row.dart only use FrequencyInterval constructor + .label() — they do NOT reference .presets.
The .presets list can safely stop being used in the UI without breaking anything.
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Rework frequency picker — shortcut chips + freeform picker</name>
<files>lib/features/tasks/presentation/task_form_screen.dart, lib/l10n/app_de.arb, lib/l10n/app_localizations.dart, lib/l10n/app_localizations_de.dart</files>
<action>
Rework the frequency picker in `task_form_screen.dart` following the locked user decisions in 09-CONTEXT.md.
**Step 1 — Add l10n strings** to `app_de.arb`:
Add 4 new keys for shortcut chip labels:
- `"frequencyShortcutDaily": "Täglich"`
- `"frequencyShortcutWeekly": "Wöchentlich"`
- `"frequencyShortcutBiweekly": "Alle 2 Wochen"`
- `"frequencyShortcutMonthly": "Monatlich"`
Then run `flutter gen-l10n` to regenerate `app_localizations.dart` and `app_localizations_de.dart`.
**Step 2 — Define shortcut enum** in `task_form_screen.dart`:
Create a private enum `_ShortcutFrequency` with values: `daily, weekly, biweekly, monthly`.
Add a method `toPickerValues()` returning `({int number, _CustomUnit unit})`:
- daily → (1, days)
- weekly → (1, weeks)
- biweekly → (2, weeks)
- monthly → (1, months)
Add a static method `fromPickerValues(int number, _CustomUnit unit)` returning `_ShortcutFrequency?`:
- (1, days) → daily
- (1, weeks) → weekly
- (2, weeks) → biweekly
- (1, months) → monthly
- anything else → null
**Step 3 — Simplify state variables:**
Remove `_selectedPreset` (FrequencyInterval?) and `_isCustomFrequency` (bool).
Add `_activeShortcut` (_ShortcutFrequency?) — nullable, null means no chip highlighted.
Change `initState` default: instead of `_selectedPreset = FrequencyInterval.presets[3]`, set:
- `_activeShortcut = _ShortcutFrequency.weekly`
- `_customIntervalController.text = '1'` (already defaults to '2', change to '1')
- `_customUnit = _CustomUnit.weeks`
**Step 4 — Rework `_buildFrequencySelector()`:**
Replace the entire method. New structure:
```
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Shortcut chips row (always visible)
Wrap(
spacing: 8,
runSpacing: 4,
children: [
for each _ShortcutFrequency shortcut:
ChoiceChip(
label: Text(_shortcutLabel(shortcut, l10n)),
selected: _activeShortcut == shortcut,
onSelected: (selected) {
if (selected) {
final values = shortcut.toPickerValues();
setState(() {
_activeShortcut = shortcut;
_customIntervalController.text = values.number.toString();
_customUnit = values.unit;
});
}
},
),
],
),
const SizedBox(height: 12),
// Freeform picker row (ALWAYS visible — not conditional)
_buildFrequencyPickerRow(l10n, theme),
],
)
```
Add helper `_shortcutLabel(_ShortcutFrequency shortcut, AppLocalizations l10n)` returning the l10n string for each shortcut.
**Step 5 — Rename `_buildCustomFrequencyInput` to `_buildFrequencyPickerRow`:**
The method body stays almost identical. One change: when the user edits the number field or changes the unit, recalculate `_activeShortcut`:
- In the TextFormField's `onChanged` callback: `setState(() { _activeShortcut = _ShortcutFrequency.fromPickerValues(int.tryParse(_customIntervalController.text) ?? 1, _customUnit); })`
- In the SegmentedButton's `onSelectionChanged`: after setting `_customUnit`, also recalculate: `_activeShortcut = _ShortcutFrequency.fromPickerValues(int.tryParse(_customIntervalController.text) ?? 1, newUnit);`
This ensures bidirectional sync: chip → picker and picker → chip.
**Step 6 — Simplify `_resolveFrequency()`:**
Remove the `if (!_isCustomFrequency && _selectedPreset != null)` branch entirely.
The method now ALWAYS reads from the picker values (`_customIntervalController` + `_customUnit`), since the picker is always the source of truth (shortcuts just populate it). Use the SMART mapping that matches existing DB behavior for named types:
- 1 day → IntervalType.daily, days=1
- N days (N>1) → IntervalType.everyNDays, days=N
- 1 week → IntervalType.weekly, days=1
- 2 weeks → IntervalType.biweekly, days=14
- N weeks (N>2) → IntervalType.everyNDays, days=N*7
- 1 month → IntervalType.monthly, days=1, anchorDay=dueDate.day
- N months (N>1) → IntervalType.everyNMonths, days=N, anchorDay=dueDate.day
CRITICAL correctness note: The existing weekly preset has `days=1` (it's a named type where `intervalDays` stores 1). The old custom weeks path returns `everyNDays` with `days=N*7`. The new unified `_resolveFrequency` MUST use the named types (daily/weekly/biweekly/monthly) for their canonical values to match existing DB records. Only use everyNDays for non-canonical week counts (3+ weeks). Similarly, monthly uses `days=1` (not days=30) since it's a named type.
**Step 7 — Rework `_loadExistingTask()` for edit mode:**
Replace the preset-matching loop (lines 69-78) and custom-detection logic (lines 80-98) with unified picker population:
```dart
// Populate picker from stored interval
switch (task.intervalType) {
case IntervalType.daily:
_customUnit = _CustomUnit.days;
_customIntervalController.text = '1';
case IntervalType.everyNDays:
// Check if it's a clean week multiple
if (task.intervalDays % 7 == 0) {
_customUnit = _CustomUnit.weeks;
_customIntervalController.text = (task.intervalDays ~/ 7).toString();
} else {
_customUnit = _CustomUnit.days;
_customIntervalController.text = task.intervalDays.toString();
}
case IntervalType.weekly:
_customUnit = _CustomUnit.weeks;
_customIntervalController.text = '1';
case IntervalType.biweekly:
_customUnit = _CustomUnit.weeks;
_customIntervalController.text = '2';
case IntervalType.monthly:
_customUnit = _CustomUnit.months;
_customIntervalController.text = '1';
case IntervalType.everyNMonths:
_customUnit = _CustomUnit.months;
_customIntervalController.text = task.intervalDays.toString();
case IntervalType.quarterly:
_customUnit = _CustomUnit.months;
_customIntervalController.text = '3';
case IntervalType.yearly:
_customUnit = _CustomUnit.months;
_customIntervalController.text = '12';
}
// Detect matching shortcut chip
_activeShortcut = _ShortcutFrequency.fromPickerValues(
int.tryParse(_customIntervalController.text) ?? 1,
_customUnit,
);
```
This handles ALL 8 IntervalType values correctly, including quarterly (3 months) and yearly (12 months) which have no shortcut chip but display correctly in the picker.
**Step 8 — Clean up unused references:**
- Remove `_selectedPreset` field
- Remove `_isCustomFrequency` field
- Remove the import or reference to `FrequencyInterval.presets` in the chip-building code (the `for (final preset in FrequencyInterval.presets)` loop)
- Keep the `taskFormFrequencyCustom` l10n key in the ARB file (do NOT delete l10n keys — they're harmless and removing requires regen)
- Do NOT modify `frequency.dart` — the `presets` list stays for backward compatibility even though the UI no longer iterates it
**Verification notes for _resolveFrequency:**
The key correctness requirement is that saving a task from the new picker produces EXACTLY the same IntervalType + intervalDays + anchorDay as the old preset path did for equivalent selections. Verify by mentally tracing:
- Chip "Taeglich" → picker (1, days) → resolves to (daily, 1, null) -- matches old preset[0]
- Chip "Woechentlich" → picker (1, weeks) → resolves to (weekly, 1, null) -- matches old preset[3]
- Chip "Alle 2 Wochen" → picker (2, weeks) → resolves to (biweekly, 14, null) -- matches old preset[4]
- Chip "Monatlich" → picker (1, months) → resolves to (monthly, 1, anchorDay) -- matches old preset[5]
- Freeform "5 days" → picker (5, days) → resolves to (everyNDays, 5, null) -- matches old custom path
- Freeform "3 months" → picker (3, months) → resolves to (everyNMonths, 3, anchorDay) -- matches old custom path
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos 2>&1 | tail -5 && flutter test 2>&1 | tail -10</automated>
</verify>
<done>
- The 10-chip Wrap grid is fully replaced by 4 shortcut chips + always-visible freeform picker
- The "Benutzerdefiniert" (Custom) chip is removed — the picker is inherently freeform
- Bidirectional sync: tapping a chip populates the picker; editing the picker recalculates chip highlight
- `_resolveFrequency()` reads exclusively from the picker (single source of truth)
- Edit mode correctly loads all 8 IntervalType values into the picker and highlights matching shortcut chip
- All existing tests pass, dart analyze is clean
- No changes to frequency.dart, no DB migration, no new screens
</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 2: Verify frequency picker UX</name>
<what-built>Reworked frequency picker with 4 shortcut chips and freeform "Every [N] [unit]" picker, replacing the old 10-chip grid + hidden Custom mode</what-built>
<how-to-verify>
1. Launch the app: `flutter run`
2. Navigate to any room and tap "+" to create a new task
3. Verify the frequency section shows:
- Row of 4 shortcut chips: Taeglich, Woechentlich, Alle 2 Wochen, Monatlich
- Below: always-visible freeform picker row with number field + Tage/Wochen/Monate segments
- "Woechentlich" chip highlighted by default, picker showing "1" with "Wochen" selected
4. Tap "Taeglich" chip — verify chip highlights and picker updates to "1" / "Tage"
5. Tap "Monatlich" chip — verify chip highlights and picker updates to "1" / "Monate"
6. Manually type "5" in the number field — verify all chips deselect (no shortcut matches 5 weeks)
7. Change unit to "Tage" — verify still no chip selected (5 days is not a shortcut)
8. Type "1" in the number field with "Tage" selected — verify "Taeglich" chip auto-highlights
9. Save a task with "Alle 2 Wochen" shortcut, then re-open in edit mode — verify "Alle 2 Wochen" chip is highlighted and picker shows "2" / "Wochen"
10. If you have an existing quarterly or yearly task, open it in edit mode — verify no chip highlighted, picker shows "3" / "Monate" (quarterly) or "12" / "Monate" (yearly)
</how-to-verify>
<resume-signal>Type "approved" or describe issues</resume-signal>
</task>
</tasks>
<verification>
- `flutter analyze --no-fatal-infos` reports zero issues
- `flutter test` — all existing tests pass (108+)
- Manual verification: create task with each shortcut, create task with arbitrary interval, edit existing tasks of all interval types
</verification>
<success_criteria>
1. The frequency section presents 4 shortcut chips above an always-visible "Every [N] [unit]" picker (TCX-01, TCX-02)
2. Any arbitrary interval is settable directly in the picker without a "Custom" mode (TCX-03)
3. All 8 IntervalType values save and load correctly, including calendar-anchored monthly/quarterly/yearly with anchor memory (TCX-04)
4. Existing tests pass without modification, dart analyze is clean
</success_criteria>
<output>
After completion, create `.planning/phases/09-task-creation-ux/09-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,105 @@
---
phase: 09-task-creation-ux
plan: 01
subsystem: ui
tags: [flutter, dart, l10n, frequency-picker, choice-chip, segmented-button]
# Dependency graph
requires: []
provides:
- Reworked frequency picker with 4 shortcut chips (Täglich, Wöchentlich, Alle 2 Wochen, Monatlich)
- Always-visible freeform "Alle [N] [unit]" picker replacing hidden Custom mode
- Bidirectional chip/picker sync via _ShortcutFrequency enum
- Unified _resolveFrequency() reading exclusively from picker (single source of truth)
- Edit mode loading for all 8 IntervalType values including quarterly and yearly
affects: []
# Tech tracking
tech-stack:
added: []
patterns:
- "Shortcut chip + freeform picker: ChoiceChip row above always-visible SegmentedButton picker"
- "Bidirectional sync: chip tapped populates picker; picker edited recalculates chip highlight via fromPickerValues()"
- "Single source of truth: _resolveFrequency() always reads from picker, never from a preset reference"
key-files:
created: []
modified:
- lib/features/tasks/presentation/task_form_screen.dart
- lib/l10n/app_de.arb
- lib/l10n/app_localizations.dart
- lib/l10n/app_localizations_de.dart
key-decisions:
- "Picker is single source of truth: _resolveFrequency() reads from _customIntervalController + _customUnit always"
- "_ShortcutFrequency enum with toPickerValues() and fromPickerValues() handles bidirectional sync without manual mapping"
- "Named IntervalTypes (daily/weekly/biweekly/monthly) used for canonical values; only everyNDays for 3+ weeks"
- "Quarterly (3 months) and yearly (12 months) displayed correctly in picker with no chip highlighted"
patterns-established:
- "Shortcut chip pattern: enum with toPickerValues() / fromPickerValues() for bidirectional picker sync"
requirements-completed: [TCX-01, TCX-02, TCX-03, TCX-04]
# Metrics
duration: 2min
completed: 2026-03-18
---
# Phase 9 Plan 01: Task Creation UX — Frequency Picker Rework Summary
**4 shortcut chips (Täglich/Wöchentlich/Alle 2 Wochen/Monatlich) + always-visible freeform picker replacing the 10-chip grid with hidden Custom mode**
## Performance
- **Duration:** 2 min
- **Started:** 2026-03-18T21:43:24Z
- **Completed:** 2026-03-18T21:45:30Z
- **Tasks:** 1 (+ 1 auto-approved checkpoint)
- **Files modified:** 4
## Accomplishments
- Replaced 10-chip preset grid and hidden "Benutzerdefiniert" mode with 4 shortcut chips + always-visible freeform picker
- Implemented bidirectional sync: tapping a chip populates the picker; editing the picker recalculates chip highlight
- Simplified _resolveFrequency() to read exclusively from the picker (single source of truth), using named IntervalTypes for canonical values
- Edit mode correctly loads all 8 IntervalType values (daily, everyNDays, weekly, biweekly, monthly, everyNMonths, quarterly, yearly) into the picker and highlights the matching shortcut chip where applicable
## Task Commits
Each task was committed atomically:
1. **Task 1: Rework frequency picker — shortcut chips + freeform picker** - `8a0b69b` (feat)
2. **Task 2: Verify frequency picker UX** - auto-approved (checkpoint:human-verify, auto_advance=true)
**Plan metadata:** (docs commit follows)
## Files Created/Modified
- `lib/features/tasks/presentation/task_form_screen.dart` - Reworked frequency picker: removed _selectedPreset and _isCustomFrequency fields; added _ShortcutFrequency enum and _activeShortcut state; replaced _buildFrequencySelector() with shortcut chips + always-visible picker; renamed _buildCustomFrequencyInput to _buildFrequencyPickerRow with bidirectional sync; simplified _resolveFrequency() to picker-only
- `lib/l10n/app_de.arb` - Added frequencyShortcutDaily/Weekly/Biweekly/Monthly keys
- `lib/l10n/app_localizations.dart` - Regenerated to include new shortcut string getters
- `lib/l10n/app_localizations_de.dart` - Regenerated with German translations (Täglich, Wöchentlich, Alle 2 Wochen, Monatlich)
## Decisions Made
- Picker is single source of truth: _resolveFrequency() reads from _customIntervalController + _customUnit always, regardless of which chip is highlighted
- _ShortcutFrequency enum with toPickerValues() and static fromPickerValues() cleanly handles bidirectional sync without manual if-chain mapping in each callback
- Named IntervalTypes (daily/weekly/biweekly/monthly) used for canonical values (e.g., weekly has days=1, biweekly has days=14) matching existing DB records; only everyNDays used for 3+ weeks
- Quarterly (3 months) and yearly (12 months) round-trip correctly: loaded as "3 Monate" / "12 Monate" in picker with no chip highlighted
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Frequency picker rework complete; TaskFormScreen is ready for further UX improvements
- All 144 existing tests pass, dart analyze is clean
- No changes to frequency.dart, no DB migration, no new screens
---
*Phase: 09-task-creation-ux*
*Completed: 2026-03-18*

View File

@@ -0,0 +1,117 @@
# Phase 9: Task Creation UX - Context
**Gathered:** 2026-03-18
**Status:** Ready for planning
<domain>
## Phase Boundary
Rework the frequency picker from a flat grid of 10 preset ChoiceChips + hidden "Custom" mode into an intuitive "Every [N] [unit]" picker with quick-select shortcut chips. The picker is inherently freeform — no separate "Custom" mode. All existing interval types and calendar-anchored scheduling behavior must continue working. No new scheduling logic, no new DB columns, no new screens.
</domain>
<decisions>
## Implementation Decisions
### Picker layout
- Shortcut chips first (compact row), then the "Every [N] [unit]" picker row below
- Tapping a chip highlights it AND populates the picker row (bidirectional sync)
- Editing the picker manually deselects any highlighted chip
- Both always reflect the same value — single source of truth
- The picker row is always visible, not hidden behind a "Custom" toggle
### Number input
- Keep the existing TextFormField with digit-only filter
- Same pattern as the current custom interval input (TextFormField + FilteringTextInputFormatter.digitsOnly)
- Minimum value: 1 (no max limit — if someone wants every 999 days, let them)
- Text field centered, compact width (~60px as current)
### Unit selector
- Keep SegmentedButton with 3 units: Tage (days), Wochen (weeks), Monate (months)
- No "years" unit — yearly is handled as 12 months in the picker
- Consistent with existing SegmentedButton pattern used for effort level selector
### Shortcut chips
- 4 shortcut chips: Täglich, Wöchentlich, Alle 2 Wochen, Monatlich
- No quarterly/yearly shortcut chips — users type 3/12 months via the freeform picker
- Drop all other presets (every 2 days, every 3 days, every 2 months, every 6 months) — the freeform picker handles arbitrary intervals naturally
- Chips use ChoiceChip widget (existing pattern)
### Preset removal
- Remove the entire `FrequencyInterval.presets` static list from being used in the UI
- The 10-chip Wrap grid is fully replaced by 4 shortcut chips + freeform picker
- The "Benutzerdefiniert" (Custom) chip is removed — the picker is inherently freeform
- `_isCustomFrequency` boolean state is no longer needed
### Edit mode behavior
- When editing an existing task, match the stored interval to a shortcut chip if possible
- Daily task → highlight "Täglich" chip, picker shows "Alle 1 Tage"
- Weekly task → highlight "Wöchentlich" chip, picker shows "Alle 1 Wochen"
- Biweekly → highlight "Alle 2 Wochen" chip, picker shows "Alle 2 Wochen"
- Monthly → highlight "Monatlich" chip, picker shows "Alle 1 Monate"
- Any other interval (e.g., every 5 days, quarterly, yearly) → no chip highlighted, picker filled with correct values
### DB mapping
- Shortcut chips map to existing IntervalType enum values (daily, weekly, biweekly, monthly)
- Freeform days → IntervalType.everyNDays with the entered value
- Freeform weeks → IntervalType.everyNDays with value * 7
- Freeform months → IntervalType.everyNMonths with the entered value
- Calendar-anchored behavior (anchorDay) preserved for month-based intervals
- No changes to IntervalType enum, no new DB values, no migration needed
### Claude's Discretion
- Exact chip styling and spacing within the Wrap
- Animation/transition when syncing chip ↔ picker
- Whether the "Alle" prefix label is part of the picker row or omitted
- How to handle the edge case where user clears the number field (empty → treat as 1)
- l10n string changes needed for new/modified labels
</decisions>
<specifics>
## Specific Ideas
- User consistently prefers simplicity across phases ("just keep it simple" — Phase 8 pattern)
- The key UX improvement: no more hunting through 10 chips or finding a hidden "Custom" mode — the picker is always there and always works
- 4 common shortcuts for one-tap convenience, freeform picker for everything else
- The current `_buildCustomFrequencyInput` method is essentially what becomes the primary picker — it already has the right structure
</specifics>
<code_context>
## Existing Code Insights
### Reusable Assets
- `_buildCustomFrequencyInput()` in `task_form_screen.dart:279-326`: Already implements "Alle [N] [Tage|Wochen|Monate]" row with TextFormField + SegmentedButton — this becomes the primary picker
- `_CustomUnit` enum (`task_form_screen.dart:511`): Already has days/weeks/months — reuse directly
- `_customIntervalController` (`task_form_screen.dart:35`): Already exists for the number input
- `_resolveFrequency()` (`task_form_screen.dart:378-415`): Already handles custom unit → IntervalType mapping — core logic stays the same
- `_loadExistingTask()` (`task_form_screen.dart:55-101`): Already has edit-mode loading logic with preset matching — needs rework for new chip set
- `FrequencyInterval.presets` (`frequency.dart:50-61`): Static list of 10 presets — UI no longer iterates this, but the model class stays for backward compat
### Established Patterns
- ChoiceChip in Wrap for multi-option selection (current frequency chips)
- SegmentedButton for unit/level selection (effort level, custom unit)
- TextFormField with FilteringTextInputFormatter for numeric input
- ConsumerStatefulWidget with setState for form state management
- German l10n strings in `app_de.arb` via `AppLocalizations`
### Integration Points
- `task_form_screen.dart`: Primary file — rework `_buildFrequencySelector()` method, simplify state variables
- `frequency.dart`: `FrequencyInterval.presets` list is no longer iterated in UI but may still be used elsewhere (templates) — check before removing
- `app_de.arb` / `app_localizations.dart`: May need new/updated l10n keys for shortcut chip labels
- `template_picker_sheet.dart` / `task_templates.dart`: Templates create tasks with specific IntervalType values — no changes needed since DB mapping unchanged
</code_context>
<deferred>
## Deferred Ideas
None — discussion stayed within phase scope
</deferred>
---
*Phase: 09-task-creation-ux*
*Context gathered: 2026-03-18*

View File

@@ -0,0 +1,161 @@
---
phase: 09-task-creation-ux
verified: 2026-03-18T23:00:00Z
status: human_needed
score: 8/8 must-haves verified
human_verification:
- test: "Create new task — verify frequency section layout"
expected: "4 shortcut chips (Taeglich, Woechentlich, Alle 2 Wochen, Monatlich) appear in a Wrap row; below them an always-visible picker row shows 'Alle [number] [Tage|Wochen|Monate]'; 'Woechentlich' chip is highlighted by default with picker showing '1' and 'Wochen' selected"
why_human: "Visual layout and default highlight state require running the app"
- test: "Tap each shortcut chip and verify bidirectional sync"
expected: "Tapping 'Taeglich' highlights that chip and sets picker to '1'/'Tage'; tapping 'Monatlich' highlights that chip and sets picker to '1'/'Monate'; previously highlighted chip deselects"
why_human: "Widget interaction and visual chip highlight state require running the app"
- test: "Edit the number field and verify chip deselection"
expected: "With 'Woechentlich' highlighted, typing '5' in the number field deselects all chips; changing unit to 'Tage' still shows no chip; typing '1' with 'Tage' selected auto-highlights 'Taeglich'"
why_human: "Bidirectional sync from picker back to chip highlight requires running the app"
- test: "Save a task using each shortcut and verify re-open in edit mode"
expected: "Task saved with 'Alle 2 Wochen' reopens with that chip highlighted and picker showing '2'/'Wochen'; task saved with arbitrary interval (e.g. 5 days) reopens with no chip highlighted and picker showing the correct values"
why_human: "Round-trip edit-mode loading of IntervalType values requires running the app"
- test: "Verify quarterly and yearly tasks load with no chip highlighted"
expected: "An existing quarterly task (IntervalType.quarterly) opens with no chip highlighted and picker showing '3'/'Monate'; a yearly task shows '12'/'Monate' with no chip"
why_human: "Requires an existing quarterly or yearly task in the database to test against"
---
# Phase 9: Task Creation UX Verification Report
**Phase Goal:** Users can set any recurring frequency intuitively without hunting through a grid of preset chips — common frequencies are one tap away, custom intervals are freeform
**Verified:** 2026-03-18T23:00:00Z
**Status:** human_needed
**Re-verification:** No — initial verification
---
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Frequency section shows 4 shortcut chips (Taeglich, Woechentlich, Alle 2 Wochen, Monatlich) above the freeform picker | VERIFIED | `_buildFrequencySelector()` at line 234 iterates `_ShortcutFrequency.values` in a `Wrap` with `ChoiceChip` for each of the 4 values; `_buildFrequencyPickerRow()` is called unconditionally below the Wrap |
| 2 | The freeform 'Every [N] [unit]' picker row is always visible — not hidden behind a Custom toggle | VERIFIED | `_buildFrequencyPickerRow(l10n, theme)` is called unconditionally at line 262 with no conditional wrapping; `_isCustomFrequency` field removed entirely |
| 3 | Tapping a shortcut chip highlights it AND populates the picker with the corresponding values | VERIFIED | `onSelected` at line 247 calls `shortcut.toPickerValues()` and `setState` setting both `_activeShortcut = shortcut` and updating `_customIntervalController.text` + `_customUnit`; `selected: _activeShortcut == shortcut` drives the highlight |
| 4 | Editing the picker number or unit manually deselects any highlighted chip | VERIFIED | `onChanged` in TextFormField at line 298 calls `_ShortcutFrequency.fromPickerValues(...)` — returns null for non-matching values, clearing `_activeShortcut`; `SegmentedButton.onSelectionChanged` at line 326 does the same |
| 5 | Any arbitrary interval (e.g., every 5 days, every 3 weeks, every 2 months) can be entered directly in the freeform picker | VERIFIED | Picker is a `TextFormField` with `FilteringTextInputFormatter.digitsOnly` (no max) and a 3-segment unit selector; `_resolveFrequency()` at line 393 maps all day/week/month combinations to the correct `IntervalType` values without requiring any mode switch |
| 6 | Editing an existing daily task shows 'Taeglich' chip highlighted and picker showing 1/Tage | VERIFIED | `_loadExistingTask()` at line 56: `case IntervalType.daily` sets `_customUnit = _CustomUnit.days` and `_customIntervalController.text = '1'`; then `_ShortcutFrequency.fromPickerValues(1, days)` returns `daily` — highlighting the chip |
| 7 | Editing an existing quarterly task (3 months) shows no chip highlighted and picker showing 3/Monate | VERIFIED | `case IntervalType.quarterly` sets `_customUnit = _CustomUnit.months` and `_customIntervalController.text = '3'`; `fromPickerValues(3, months)` returns `null` (3 months is not a shortcut), leaving `_activeShortcut` null |
| 8 | Saving a task from the new picker produces the correct IntervalType and intervalDays values | VERIFIED | `_resolveFrequency()` maps: 1 day → (daily, 1); N days → (everyNDays, N); 1 week → (weekly, 1); 2 weeks → (biweekly, 14); N weeks (N>2) → (everyNDays, N*7); 1 month → (monthly, 1, anchorDay); N months → (everyNMonths, N, anchorDay). Result is applied in `_onSave()` at line 423. All 144 existing tests pass. |
**Score:** 8/8 truths verified (automated)
---
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `lib/features/tasks/presentation/task_form_screen.dart` | Reworked frequency picker with shortcut chips + freeform picker | VERIFIED | File exists, 536 lines; contains `_ShortcutFrequency` enum (line 504), `_activeShortcut` state (line 37), `_buildFrequencySelector()` (line 234), `_buildFrequencyPickerRow()` (line 280), `_resolveFrequency()` (line 393), `_loadExistingTask()` (line 56) |
| `lib/l10n/app_de.arb` | German l10n strings for shortcut chip labels | VERIFIED | Contains `frequencyShortcutDaily` ("Täglich"), `frequencyShortcutWeekly` ("Wöchentlich"), `frequencyShortcutBiweekly` ("Alle 2 Wochen"), `frequencyShortcutMonthly` ("Monatlich") at lines 51-54 |
| `lib/l10n/app_localizations.dart` | Abstract getters for new shortcut strings | VERIFIED | `frequencyShortcutDaily`, `frequencyShortcutWeekly`, `frequencyShortcutBiweekly`, `frequencyShortcutMonthly` abstract getters present at lines 325-347 |
| `lib/l10n/app_localizations_de.dart` | German implementations of shortcut string getters | VERIFIED | All 4 `@override` getter implementations present at lines 132-141 with correct German strings |
---
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `task_form_screen.dart` | `lib/features/tasks/domain/frequency.dart` | `IntervalType` enum in `_resolveFrequency()` and `_loadExistingTask()` | WIRED | `IntervalType.` referenced at 13 sites (lines 71, 74, 83, 86, 89, 92, 95, 98, 398, 400, 403, 406, 408, 411, 413); all 8 enum values handled; `frequency.dart` imported via `../domain/frequency.dart` |
| `task_form_screen.dart` | `lib/l10n/app_de.arb` | `AppLocalizations` for chip labels via `l10n.frequencyShortcut*` | WIRED | `l10n.frequencyShortcutDaily/Weekly/Biweekly/Monthly` called at lines 270-276 in `_shortcutLabel()`; `AppLocalizations` imported at line 10 |
---
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|------------|-------------|--------|----------|
| TCX-01 | 09-01-PLAN.md | Frequency picker presents an intuitive "Every [N] [unit]" interface instead of a flat grid of preset chips | SATISFIED | `_buildFrequencyPickerRow()` always-visible TextFormField + SegmentedButton row replaces the old 10-chip `FrequencyInterval.presets` grid; `_isCustomFrequency` and `_selectedPreset` removed entirely |
| TCX-02 | 09-01-PLAN.md | Common frequencies (daily, weekly, biweekly, monthly) are available as quick-select shortcuts without scrolling through all options | SATISFIED | `_ShortcutFrequency.values` iterated in a `Wrap` at lines 243-257; 4 ChoiceChips one-tap select and populate the picker |
| TCX-03 | 09-01-PLAN.md | User can set any arbitrary interval without needing to select "Custom" first | SATISFIED | Picker is always visible; number field accepts any positive integer; no mode gate or "Custom" toggle exists in the code |
| TCX-04 | 09-01-PLAN.md | The frequency picker preserves all existing interval types and scheduling behavior (calendar-anchored monthly/quarterly/yearly with anchor memory) | SATISFIED | `_resolveFrequency()` passes `anchorDay: _dueDate.day` for monthly and everyNMonths; `_loadExistingTask()` handles all 8 `IntervalType` values in a complete exhaustive switch; `frequency.dart` not modified; 144 tests pass |
No orphaned requirements found — all 4 TCX-* IDs declared in PLAN frontmatter are present in REQUIREMENTS.md and verified above.
---
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| None | — | — | — | — |
No TODOs, FIXMEs, placeholders, empty implementations, or console.log-only handlers found in any modified file.
**Removed code confirmed absent:**
- `_selectedPreset` field: not found
- `_isCustomFrequency` field: not found
- `FrequencyInterval.presets` iteration loop: not found
- `_buildCustomFrequencyInput` (old name): not found (correctly renamed to `_buildFrequencyPickerRow`)
**Backward compatibility confirmed:**
- `frequency.dart` is unchanged; `FrequencyInterval.presets` remains in the model for `template_picker_sheet.dart` and `task_row.dart` usage
---
### Human Verification Required
All automated checks passed. The following items require a running app to confirm the interactive UX behavior:
#### 1. Frequency Section Layout
**Test:** Launch the app, navigate to any room, tap "+" to create a new task, scroll to the "Wiederholung" section.
**Expected:** A row of 4 shortcut chips (Täglich, Wöchentlich, Alle 2 Wochen, Monatlich) appears in a compact Wrap; below them the always-visible "Alle [N] [Tage|Wochen|Monate]" picker row; "Wöchentlich" chip is highlighted by default; picker shows "1" with "Wochen" segment selected.
**Why human:** Visual layout, spacing, and default highlight state require running the app.
#### 2. Chip-to-Picker Bidirectional Sync
**Test:** Tap "Täglich" — verify chip highlights and picker updates to "1"/"Tage". Tap "Monatlich" — verify chip highlights and picker updates to "1"/"Monate". Previous chip deselects.
**Expected:** Smooth single-tap update of both chip highlight and picker values.
**Why human:** Widget interaction and visual state transitions require running the app.
#### 3. Picker-to-Chip Reverse Sync
**Test:** With "Wöchentlich" highlighted, type "5" in the number field. Verify all chips deselect. Change unit to "Tage" — still no chip selected. Type "1" with "Tage" selected — verify "Täglich" chip auto-highlights.
**Expected:** The picker editing recalculates chip highlight in real time.
**Why human:** Text field onChange and SegmentedButton interaction require running the app.
#### 4. Round-Trip Edit Mode — Shortcut Task
**Test:** Create a task using "Alle 2 Wochen" shortcut. Re-open it in edit mode.
**Expected:** "Alle 2 Wochen" chip is highlighted; picker shows "2" with "Wochen" selected.
**Why human:** Requires saving to database and reopening to test _loadExistingTask() end-to-end.
#### 5. Round-Trip Edit Mode — Non-Shortcut Task
**Test:** Create a task with freeform "5"/"Tage". Re-open it in edit mode.
**Expected:** No chip highlighted; picker shows "5" with "Tage" selected.
**Why human:** Requires running the app and database round-trip.
#### 6. Quarterly / Yearly Task Edit Mode
**Test:** If an existing quarterly or yearly task is available, open it in edit mode.
**Expected:** Quarterly task: no chip highlighted, picker shows "3"/"Monate". Yearly task: picker shows "12"/"Monate" with no chip.
**Why human:** Requires an existing task with IntervalType.quarterly or IntervalType.yearly in the database.
---
### Static Analysis and Tests
- `flutter analyze --no-fatal-infos`: **No issues found** (ran 2026-03-18)
- `flutter test`: **144/144 tests passed** (ran 2026-03-18)
- Commit `8a0b69b` verified: feat(09-01) with correct 4-file diff (task_form_screen.dart +179/-115, app_de.arb +4, app_localizations.dart +24, app_localizations_de.dart +12)
---
### Gaps Summary
No gaps found. All automated must-haves are verified. The phase goal — intuitive frequency selection with shortcut chips and always-visible freeform picker — is fully implemented in the codebase. Human verification of interactive UX behavior is the only remaining item.
---
_Verified: 2026-03-18T23:00:00Z_
_Verifier: Claude (gsd-verifier)_

View File

@@ -0,0 +1,134 @@
---
phase: 10-dead-code-cleanup
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- lib/features/home/presentation/daily_plan_providers.dart
- lib/features/home/presentation/daily_plan_task_row.dart
- lib/features/home/presentation/progress_card.dart
- lib/features/home/domain/daily_plan_models.dart
autonomous: true
requirements: [CLN-01]
must_haves:
truths:
- "daily_plan_providers.dart no longer exists in the codebase"
- "daily_plan_task_row.dart no longer exists in the codebase"
- "progress_card.dart no longer exists in the codebase"
- "DailyPlanDao is still registered in database.dart and functional"
- "TaskWithRoom class still exists and is importable by calendar system"
- "All 144 tests pass without failures"
- "dart analyze reports zero issues"
artifacts:
- path: "lib/features/home/domain/daily_plan_models.dart"
provides: "TaskWithRoom class (DailyPlanState removed)"
contains: "class TaskWithRoom"
key_links:
- from: "lib/features/home/data/calendar_dao.dart"
to: "lib/features/home/domain/daily_plan_models.dart"
via: "import for TaskWithRoom"
pattern: "import.*daily_plan_models"
- from: "lib/features/home/presentation/calendar_providers.dart"
to: "lib/features/home/domain/daily_plan_models.dart"
via: "import for TaskWithRoom"
pattern: "import.*daily_plan_models"
- from: "lib/core/database/database.dart"
to: "lib/features/home/data/daily_plan_dao.dart"
via: "DAO registration in @DriftDatabase annotation"
pattern: "DailyPlanDao"
---
<objective>
Delete three orphaned v1.0 daily plan presentation files and clean up the orphaned DailyPlanState class from the domain models file, then verify no regressions.
Purpose: These files were superseded by the calendar strip (Phase 5, v1.1) but never removed. Cleaning them prevents confusion and reduces maintenance surface.
Output: Three files deleted, one file trimmed, zero test/analysis regressions.
</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
</context>
<tasks>
<task type="auto">
<name>Task 1: Delete orphaned files and remove DailyPlanState</name>
<files>
lib/features/home/presentation/daily_plan_providers.dart (DELETE)
lib/features/home/presentation/daily_plan_task_row.dart (DELETE)
lib/features/home/presentation/progress_card.dart (DELETE)
lib/features/home/domain/daily_plan_models.dart (MODIFY)
</files>
<action>
1. Delete these three files entirely (use `rm` or equivalent):
- lib/features/home/presentation/daily_plan_providers.dart
- lib/features/home/presentation/daily_plan_task_row.dart
- lib/features/home/presentation/progress_card.dart
2. Edit lib/features/home/domain/daily_plan_models.dart:
- Remove the DailyPlanState class (lines 16-31) entirely. It is only used by the now-deleted daily_plan_providers.dart.
- Keep the TaskWithRoom class intact — it is used by calendar_dao.dart, calendar_models.dart, calendar_providers.dart, calendar_day_list.dart, calendar_task_row.dart, and daily_plan_dao.dart.
- Keep the existing import of database.dart at line 1.
3. DO NOT touch these files (they are still in use):
- lib/features/home/data/daily_plan_dao.dart (used by database.dart daos list and settings_screen.dart)
- lib/features/home/data/daily_plan_dao.g.dart (generated, paired with DAO)
</action>
<verify>
<automated>ls lib/features/home/presentation/daily_plan_providers.dart lib/features/home/presentation/daily_plan_task_row.dart lib/features/home/presentation/progress_card.dart 2>&1 | grep -c "No such file" | grep -q 3 && grep -c "DailyPlanState" lib/features/home/domain/daily_plan_models.dart | grep -q 0 && grep -c "TaskWithRoom" lib/features/home/domain/daily_plan_models.dart | grep -qv 0 && echo "PASS" || echo "FAIL"</automated>
</verify>
<done>Three dead files deleted, DailyPlanState removed from daily_plan_models.dart, TaskWithRoom preserved</done>
</task>
<task type="auto">
<name>Task 2: Verify zero regressions</name>
<files>
(no files modified — verification only)
</files>
<action>
1. Run `dart analyze` from the project root. Must report "No issues found!" with zero errors, warnings, or infos. If any issues appear related to the deleted files (unused imports, missing references), fix them — but based on codebase analysis, none are expected since the three files have zero importers.
2. Run `flutter test` from the project root. All 144 tests must pass. No test references the deleted files or DailyPlanState (confirmed via grep during planning).
3. If dart analyze reveals any issue (unexpected import of deleted file elsewhere), fix the import. This is a safety net — grep during planning found zero references, but the analyzer is authoritative.
</action>
<verify>
<automated>dart analyze 2>&1 | tail -1 | grep -q "No issues found" && flutter test --reporter compact 2>&1 | tail -1 | grep -q "All tests passed" && echo "PASS" || echo "FAIL"</automated>
</verify>
<done>dart analyze reports zero issues AND all 144+ tests pass — no regressions from dead code removal</done>
</task>
</tasks>
<verification>
1. `ls lib/features/home/presentation/daily_plan_providers.dart` returns "No such file"
2. `ls lib/features/home/presentation/daily_plan_task_row.dart` returns "No such file"
3. `ls lib/features/home/presentation/progress_card.dart` returns "No such file"
4. `grep "DailyPlanDao" lib/core/database/database.dart` still shows the DAO in the daos list
5. `grep "TaskWithRoom" lib/features/home/domain/daily_plan_models.dart` still shows the class
6. `grep "DailyPlanState" lib/features/home/domain/daily_plan_models.dart` returns no matches
7. `dart analyze` reports zero issues
8. `flutter test` — all tests pass
</verification>
<success_criteria>
- Three orphaned presentation files are deleted from the codebase
- DailyPlanState class is removed from daily_plan_models.dart
- TaskWithRoom class is preserved in daily_plan_models.dart
- DailyPlanDao is preserved and still registered in database.dart
- `dart analyze` reports zero issues
- All 144+ tests pass
</success_criteria>
<output>
After completion, create `.planning/phases/10-dead-code-cleanup/10-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,107 @@
---
phase: 10-dead-code-cleanup
plan: 01
subsystem: ui
tags: [flutter, dead-code, cleanup, daily-plan, calendar]
# Dependency graph
requires:
- phase: 05-calendar-strip
provides: "Calendar strip that superseded daily_plan_providers.dart, daily_plan_task_row.dart, progress_card.dart"
provides:
- "Three orphaned v1.0 daily plan presentation files removed from codebase"
- "DailyPlanState class removed; TaskWithRoom class retained in daily_plan_models.dart"
affects: []
# Tech tracking
tech-stack:
added: []
patterns: []
key-files:
created: []
modified:
- lib/features/home/domain/daily_plan_models.dart
key-decisions:
- "DailyPlanDao kept in database.dart registration — still used by notification/settings service; only the presentation layer files were deleted"
- "TaskWithRoom retained in daily_plan_models.dart — actively imported by calendar_dao.dart, calendar_providers.dart, and related calendar files"
patterns-established: []
requirements-completed: [CLN-01]
# Metrics
duration: 5min
completed: 2026-03-19
---
# Phase 10 Plan 01: Dead Code Cleanup Summary
**Deleted three orphaned v1.0 daily plan presentation files and stripped DailyPlanState from domain models, leaving TaskWithRoom intact for the calendar system — zero test/analysis regressions across all 144 tests.**
## Performance
- **Duration:** ~5 min
- **Started:** 2026-03-19T00:00:54Z
- **Completed:** 2026-03-19T00:05:00Z
- **Tasks:** 2
- **Files modified:** 4 (3 deleted, 1 trimmed)
## Accomplishments
- Deleted `daily_plan_providers.dart`, `daily_plan_task_row.dart`, and `progress_card.dart` — all orphaned since Phase 5 replaced the daily plan UI with the calendar strip
- Removed `DailyPlanState` class from `daily_plan_models.dart` (it was only referenced by the now-deleted providers file)
- Preserved `TaskWithRoom` in `daily_plan_models.dart` — confirmed it remains importable by calendar system files
- `dart analyze` reports zero issues; all 144 tests pass with no regressions
## Task Commits
Each task was committed atomically:
1. **Task 1: Delete orphaned files and remove DailyPlanState** - `510529a` (chore)
2. **Task 2: Verify zero regressions** - verification only, no file changes
**Plan metadata:** `80e7011` (docs: complete dead-code-cleanup plan)
## Files Created/Modified
- `lib/features/home/domain/daily_plan_models.dart` - Removed DailyPlanState class (lines 16-31); TaskWithRoom preserved
- `lib/features/home/presentation/daily_plan_providers.dart` - DELETED (orphaned v1.0 file)
- `lib/features/home/presentation/daily_plan_task_row.dart` - DELETED (orphaned v1.0 file)
- `lib/features/home/presentation/progress_card.dart` - DELETED (orphaned v1.0 file)
## Decisions Made
- DailyPlanDao was NOT removed from `database.dart` — it is still registered in the `@DriftDatabase` annotation and used by `settings_screen.dart`. Only the presentation layer files were deleted.
- TaskWithRoom was kept because it is imported by: `calendar_dao.dart`, `calendar_providers.dart`, `calendar_models.dart`, `calendar_day_list.dart`, `calendar_task_row.dart`, and `daily_plan_dao.dart`.
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Phase 10 dead code cleanup complete
- No blockers — dead code that was tracked as a blocker in STATE.md is now resolved
---
*Phase: 10-dead-code-cleanup*
*Completed: 2026-03-19*
## Self-Check: PASSED
- FOUND (deleted): lib/features/home/presentation/daily_plan_providers.dart
- FOUND (deleted): lib/features/home/presentation/daily_plan_task_row.dart
- FOUND (deleted): lib/features/home/presentation/progress_card.dart
- FOUND: lib/features/home/domain/daily_plan_models.dart (with TaskWithRoom, without DailyPlanState)
- FOUND: commit 510529a (chore: delete orphaned files)
- FOUND: commit 80e7011 (docs: complete plan)

View File

@@ -0,0 +1,80 @@
---
phase: 10-dead-code-cleanup
verified: 2026-03-19T00:00:00Z
status: passed
score: 7/7 must-haves verified
---
# Phase 10: Dead Code Cleanup Verification Report
**Phase Goal:** Remove orphaned v1.0 daily plan files that are no longer used after the calendar strip replacement, keeping the codebase clean
**Verified:** 2026-03-19
**Status:** PASSED
**Re-verification:** No — initial verification
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|----|--------------------------------------------------------------|------------|--------------------------------------------------------------------------|
| 1 | `daily_plan_providers.dart` no longer exists in the codebase | VERIFIED | `ls` returns "No such file or directory"; no references in lib/ |
| 2 | `daily_plan_task_row.dart` no longer exists in the codebase | VERIFIED | `ls` returns "No such file or directory"; no references in lib/ |
| 3 | `progress_card.dart` no longer exists in the codebase | VERIFIED | `ls` returns "No such file or directory"; no references in lib/ |
| 4 | `DailyPlanDao` is still registered in `database.dart` | VERIFIED | Line 51: `daos: [RoomsDao, TasksDao, DailyPlanDao, CalendarDao]` |
| 5 | `TaskWithRoom` class still exists and is importable | VERIFIED | Defined in `daily_plan_models.dart:4`; imported by 6+ calendar files |
| 6 | All 144 tests pass without failures | VERIFIED | `flutter test` output: `+144: All tests passed!` |
| 7 | `dart analyze` reports zero issues | VERIFIED | `Analyzing HouseHoldKeaper... No issues found!` |
**Score:** 7/7 truths verified
### Required Artifacts
| Artifact | Expected | Status | Details |
|-------------------------------------------------------|---------------------------------------|----------|---------------------------------------------------------------------------------------------|
| `lib/features/home/domain/daily_plan_models.dart` | TaskWithRoom class; DailyPlanState removed | VERIFIED | Contains `class TaskWithRoom` only; `DailyPlanState` grep returns no matches in entire lib/ |
| `lib/features/home/presentation/daily_plan_providers.dart` | DELETED | VERIFIED | File does not exist; confirmed by `ls` |
| `lib/features/home/presentation/daily_plan_task_row.dart` | DELETED | VERIFIED | File does not exist; confirmed by `ls` |
| `lib/features/home/presentation/progress_card.dart` | DELETED | VERIFIED | File does not exist; confirmed by `ls` |
### Key Link Verification
| From | To | Via | Status | Details |
|-------------------------------------------------------------------|-------------------------------------------------|--------------------------------------|----------|---------------------------------------------------------------------------|
| `lib/features/home/data/calendar_dao.dart` | `lib/features/home/domain/daily_plan_models.dart` | `import.*daily_plan_models` | VERIFIED | Line 4: `import '../domain/daily_plan_models.dart';` |
| `lib/features/home/presentation/calendar_providers.dart` | `lib/features/home/domain/daily_plan_models.dart` | `import.*daily_plan_models` | VERIFIED | Line 5: `import 'package:household_keeper/features/home/domain/daily_plan_models.dart';` |
| `lib/core/database/database.dart` | `lib/features/home/data/daily_plan_dao.dart` | `DailyPlanDao` in `@DriftDatabase` | VERIFIED | Line 51: `daos: [RoomsDao, TasksDao, DailyPlanDao, CalendarDao]` |
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-----------------------------------------------------------------------------------------------------------------|-----------|------------------------------------------------------------------------------------------------------|
| CLN-01 | 10-01-PLAN | Dead code from v1.0 daily plan (daily_plan_providers.dart, daily_plan_task_row.dart, progress_card.dart) is removed without breaking notification service (DailyPlanDao must be preserved) | SATISFIED | All three files deleted; DailyPlanDao still registered in database.dart; 144 tests pass; zero analyze issues |
No orphaned requirements detected. CLN-01 is the only requirement assigned to Phase 10 in REQUIREMENTS.md, and it is covered by plan 10-01.
### Anti-Patterns Found
None detected. No TODO/FIXME/placeholder comments or empty implementations found in modified files.
### Human Verification Required
None. All success criteria for this cleanup phase are programmatically verifiable: file deletion, class presence/absence, DAO registration, test pass count, and static analysis output.
### Gaps Summary
No gaps. All seven must-have truths are verified against the actual codebase:
- Three orphaned presentation files (`daily_plan_providers.dart`, `daily_plan_task_row.dart`, `progress_card.dart`) are fully deleted with no import references remaining anywhere in `lib/`.
- `DailyPlanState` class is absent from `daily_plan_models.dart`; `TaskWithRoom` is intact and actively used by 6+ calendar files.
- `DailyPlanDao` remains registered in the `@DriftDatabase` annotation on `database.dart` (line 51).
- Both task commits (`510529a`, `80e7011`) exist in git history.
- `dart analyze` reports zero issues.
- All 144 tests pass.
Phase goal is fully achieved.
---
_Verified: 2026-03-19_
_Verifier: Claude (gsd-verifier)_

60
CHANGELOG.md Normal file
View File

@@ -0,0 +1,60 @@
# Changelog
All notable changes to HouseHoldKeeper are documented in this file.
## [1.1.5] - 2026-03-17
### Fixed
- Install jq before Flutter setup in CI and release workflows (required by subosito/flutter-action)
- Remove `dart pub audit` step (not available in stable Flutter SDK on runner)
## [1.1.4] - 2026-03-17
### Added
- CI workflow for branch pushes and pull requests with static analysis, tests, security audit, and debug build
- Security gate in release workflow — CI checks must pass before release build proceeds
- F-Droid store icon (512x512) for en-US and de-DE metadata
## [1.1.3] - 2026-03-17
### Added
- Custom app launcher icon — white house on sage green background
- Adaptive icon support for Android 8+ (API 26)
- Native splash screen with themed colors (beige light / brown dark)
- Android 12+ splash screen with icon background
- F-Droid metadata (de-DE, en-US) with screenshots and descriptions
- F-Droid metadata copy step in release workflow
## [1.1.2] - 2026-03-17
### Changed
- Release workflow now sets Flutter app version from Git tag automatically
## [1.1.1] - 2026-03-17
### Added
- Integration tests for filtered and overdue task states in TaskListScreen
## [1.1.0] - 2026-03-17
### Added
- Calendar strip on home screen with day-by-day task overview
- Floating "Today" button for quick navigation
- Task history sheet showing past completions per task
- Task sorting by name, due date, or room with persistent preference
- Sort dropdown in HomeScreen and TaskListScreen
### Changed
- HomeScreen replaced with calendar-based composition
## [1.0.0] - 2026-03-16
### Added
- Initial MVP release
- Room management with drag-and-drop reordering
- Task creation with templates and custom tasks
- Recurring task scheduling (daily, weekly, monthly, yearly)
- Local notifications for due tasks
- German and English localization
- Light and dark theme support
- Local-only SQLite database (drift)

1
CLAUDE.md Normal file
View File

@@ -0,0 +1 @@
When asked to tag the current commit, your task is to ask the user whether they want it to be a patch, minor, or major release. Based on their response, you will create a git tag with the appropriate version number. Also update the CHANGELOG.md file with the new Version and the changes made since the last tag.

86
LICENSE
View File

@@ -1,73 +1,21 @@
Apache License MIT License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION Copyright (c) 2026 Jean-Luc Makiola
1. Definitions. Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. SOFTWARE.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright 2026 makiolaj
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,2 +1,65 @@
# HouseHoldKeaper # Household Keeper
Your household, effortlessly organized.
Household Keeper helps you organize and manage your household tasks. Create rooms, assign tasks, set recurring reminders, and keep your home running smoothly.
## Features
- **Room Management** — Create and organize rooms with drag-and-drop reordering
- **Task Templates** — Quickly add common household tasks or create your own
- **Recurring Scheduling** — Daily, weekly, monthly, or yearly task recurrence
- **Calendar View** — Day-by-day task overview with a floating "Today" button
- **Task History** — View past completions for each task
- **Task Sorting** — Sort by name, due date, or room with persistent preferences
- **Notifications** — Local reminders for due tasks
- **Light & Dark Theme** — Follows your system preference
- **Localization** — German and English
## Screenshots
<p float="left">
<img src="fdroid-metadata/de.jeanlucmakiola.household_keeper/en-US/phoneScreenshots/1_overview.png" width="200" />
<img src="fdroid-metadata/de.jeanlucmakiola.household_keeper/en-US/phoneScreenshots/2_create_room.png" width="200" />
<img src="fdroid-metadata/de.jeanlucmakiola.household_keeper/en-US/phoneScreenshots/3_task_templates.png" width="200" />
<img src="fdroid-metadata/de.jeanlucmakiola.household_keeper/en-US/phoneScreenshots/4_room_tasks.png" width="200" />
</p>
## Tech Stack
- **Flutter** (SDK ^3.11.0)
- **Riverpod** — State management
- **Drift** — Local SQLite database
- **GoRouter** — Navigation
- **flutter_local_notifications** — Scheduled reminders
## Getting Started
```bash
# Clone the repo
git clone https://gitea.jeanlucmakiola.de/makiolaj/HouseHoldKeaper.git
cd HouseHoldKeaper
# Install dependencies
flutter pub get
# Generate code (drift, riverpod, l10n)
dart run build_runner build --delete-conflicting-outputs
# Run the app
flutter run
```
## Building
```bash
# Debug APK
flutter build apk --debug
# Release APK (requires signing config)
flutter build apk --release
```
## License
[MIT](LICENSE) — Jean-Luc Makiola, 2026

View File

@@ -1,3 +1,6 @@
import java.io.FileInputStream
import java.util.Properties
plugins { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") id("kotlin-android")
@@ -31,25 +34,25 @@ android {
versionName = flutter.versionName versionName = flutter.versionName
} }
def keystorePropertiesFile = rootProject.file("key.properties") val keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties() val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(FileInputStream(keystorePropertiesFile))
} }
signingConfigs { signingConfigs {
release { create("release") {
keyAlias = keystoreProperties['keyAlias'] keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties['keyPassword'] keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) }
storePassword = keystoreProperties['storePassword'] storePassword = keystoreProperties.getProperty("storePassword")
} }
} }
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. // TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works. // Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.release signingConfig = signingConfigs.getByName("release")
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package com.jlmak.household_keeper package de.jeanlucmakiola.household_keeper
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show More