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>
This commit is contained in:
262
.planning/milestones/v1.1-phases/05-calendar-strip/05-01-PLAN.md
Normal file
262
.planning/milestones/v1.1-phases/05-calendar-strip/05-01-PLAN.md
Normal 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>
|
||||
@@ -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)
|
||||
316
.planning/milestones/v1.1-phases/05-calendar-strip/05-02-PLAN.md
Normal file
316
.planning/milestones/v1.1-phases/05-calendar-strip/05-02-PLAN.md
Normal 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>
|
||||
@@ -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)
|
||||
@@ -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)`; L53–54: `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)_
|
||||
105
.planning/milestones/v1.1-phases/05-calendar-strip/5-CONTEXT.md
Normal file
105
.planning/milestones/v1.1-phases/05-calendar-strip/5-CONTEXT.md
Normal 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*
|
||||
Reference in New Issue
Block a user