docs(02): create phase plan — 5 plans in 4 waves covering rooms, tasks, templates, and verification
This commit is contained in:
284
.planning/phases/02-rooms-and-tasks/02-02-PLAN.md
Normal file
284
.planning/phases/02-rooms-and-tasks/02-02-PLAN.md
Normal file
@@ -0,0 +1,284 @@
|
||||
---
|
||||
phase: 02-rooms-and-tasks
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["02-01"]
|
||||
files_modified:
|
||||
- lib/features/rooms/presentation/rooms_screen.dart
|
||||
- lib/features/rooms/presentation/room_card.dart
|
||||
- lib/features/rooms/presentation/room_form_screen.dart
|
||||
- lib/features/rooms/presentation/icon_picker_sheet.dart
|
||||
- lib/features/rooms/presentation/room_providers.dart
|
||||
- lib/core/router/router.dart
|
||||
- lib/l10n/app_de.arb
|
||||
- pubspec.yaml
|
||||
autonomous: true
|
||||
requirements:
|
||||
- ROOM-01
|
||||
- ROOM-02
|
||||
- ROOM-03
|
||||
- ROOM-04
|
||||
- ROOM-05
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "User can create a room by entering a name and selecting an icon from a curated bottom sheet picker"
|
||||
- "User can edit a room's name and icon from the room detail screen"
|
||||
- "User can delete a room with a confirmation dialog warning about cascade deletion"
|
||||
- "User can reorder rooms via drag-and-drop on the 2-column grid"
|
||||
- "Rooms screen shows a 2-column card grid with room icon, name, due task count, and thin cleanliness progress bar"
|
||||
- "Rooms screen shows empty state when no rooms exist, with a create button"
|
||||
artifacts:
|
||||
- path: "lib/features/rooms/presentation/rooms_screen.dart"
|
||||
provides: "2-column reorderable room card grid with FAB for room creation"
|
||||
min_lines: 50
|
||||
- path: "lib/features/rooms/presentation/room_card.dart"
|
||||
provides: "Room card widget with icon, name, due count, cleanliness bar"
|
||||
min_lines: 40
|
||||
- path: "lib/features/rooms/presentation/room_form_screen.dart"
|
||||
provides: "Full-screen form for room creation and editing"
|
||||
min_lines: 50
|
||||
- path: "lib/features/rooms/presentation/icon_picker_sheet.dart"
|
||||
provides: "Bottom sheet with curated Material Icons grid"
|
||||
min_lines: 30
|
||||
- path: "lib/features/rooms/presentation/room_providers.dart"
|
||||
provides: "Riverpod providers wrapping RoomsDao stream queries and mutations"
|
||||
exports: ["roomListProvider", "roomWithStatsProvider", "roomActionsProvider"]
|
||||
- path: "lib/core/router/router.dart"
|
||||
provides: "Nested routes under /rooms for room detail and forms"
|
||||
contains: "rooms/new"
|
||||
key_links:
|
||||
- from: "lib/features/rooms/presentation/room_providers.dart"
|
||||
to: "lib/features/rooms/data/rooms_dao.dart"
|
||||
via: "providers watch appDatabaseProvider then access roomsDao"
|
||||
pattern: "ref\\.watch\\(appDatabaseProvider\\)"
|
||||
- from: "lib/features/rooms/presentation/rooms_screen.dart"
|
||||
to: "lib/features/rooms/presentation/room_providers.dart"
|
||||
via: "ConsumerWidget watches roomWithStatsProvider"
|
||||
pattern: "ref\\.watch\\(roomWithStats"
|
||||
- from: "lib/features/rooms/presentation/room_card.dart"
|
||||
to: "lib/core/router/router.dart"
|
||||
via: "InkWell onTap navigates to /rooms/:roomId"
|
||||
pattern: "context\\.go.*rooms/"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the complete room management UI: 2-column reorderable card grid, room creation/edit form with icon picker, delete with confirmation, and Riverpod providers connecting to the data layer.
|
||||
|
||||
Purpose: Delivers ROOM-01 through ROOM-05 as a working user-facing feature. After this plan, users can create, view, edit, reorder, and delete rooms.
|
||||
Output: Rooms screen with card grid, room form, icon picker, providers, and router updates.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/jlmak/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/02-rooms-and-tasks/2-CONTEXT.md
|
||||
@.planning/phases/02-rooms-and-tasks/02-RESEARCH.md
|
||||
@.planning/phases/02-rooms-and-tasks/02-01-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- From Plan 01 (data layer) - the executor needs these contracts -->
|
||||
|
||||
From lib/features/rooms/data/rooms_dao.dart (created in Plan 01):
|
||||
```dart
|
||||
class RoomWithStats {
|
||||
final Room room;
|
||||
final int totalTasks;
|
||||
final int dueTasks;
|
||||
final int overdueCount;
|
||||
final double cleanlinessRatio; // 0.0 to 1.0
|
||||
}
|
||||
|
||||
@DriftAccessor(tables: [Rooms, Tasks, TaskCompletions])
|
||||
class RoomsDao extends DatabaseAccessor<AppDatabase> with _$RoomsDaoMixin {
|
||||
Stream<List<Room>> watchAllRooms();
|
||||
Stream<List<RoomWithStats>> watchRoomWithStats();
|
||||
Future<int> insertRoom(RoomsCompanion room);
|
||||
Future<bool> updateRoom(Room room);
|
||||
Future<void> deleteRoom(int roomId);
|
||||
Future<void> reorderRooms(List<int> roomIds);
|
||||
Future<Room> getRoomById(int id);
|
||||
}
|
||||
```
|
||||
|
||||
From lib/features/rooms/domain/room_icons.dart (created in Plan 01):
|
||||
```dart
|
||||
const List<({String name, IconData icon})> curatedRoomIcons = [...];
|
||||
IconData mapIconName(String name);
|
||||
```
|
||||
|
||||
From lib/core/database/database.dart (updated in Plan 01):
|
||||
```dart
|
||||
@DriftDatabase(tables: [Rooms, Tasks, TaskCompletions], daos: [RoomsDao, TasksDao])
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
RoomsDao get roomsDao => ...; // auto-generated
|
||||
TasksDao get tasksDao => ...; // auto-generated
|
||||
}
|
||||
```
|
||||
|
||||
From lib/core/providers/database_provider.dart (existing):
|
||||
```dart
|
||||
@Riverpod(keepAlive: true)
|
||||
AppDatabase appDatabase(Ref ref) { ... }
|
||||
```
|
||||
|
||||
From lib/l10n/app_de.arb (existing — 18 keys, needs expansion):
|
||||
```json
|
||||
{
|
||||
"roomsEmptyTitle": "Hier ist noch alles leer!",
|
||||
"roomsEmptyMessage": "Erstelle deinen ersten Raum, um loszulegen.",
|
||||
"roomsEmptyAction": "Raum erstellen"
|
||||
}
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create Riverpod providers, room form screen, and icon picker</name>
|
||||
<files>
|
||||
lib/features/rooms/presentation/room_providers.dart,
|
||||
lib/features/rooms/presentation/room_form_screen.dart,
|
||||
lib/features/rooms/presentation/icon_picker_sheet.dart,
|
||||
lib/core/router/router.dart,
|
||||
lib/l10n/app_de.arb,
|
||||
pubspec.yaml
|
||||
</files>
|
||||
<action>
|
||||
1. **Add flutter_reorderable_grid_view dependency**: Run `flutter pub add flutter_reorderable_grid_view` to add it to pubspec.yaml.
|
||||
|
||||
2. **lib/features/rooms/presentation/room_providers.dart**: Create Riverpod providers using @riverpod code generation:
|
||||
|
||||
```dart
|
||||
@riverpod
|
||||
Stream<List<RoomWithStats>> roomWithStatsList(Ref ref) {
|
||||
final db = ref.watch(appDatabaseProvider);
|
||||
return db.roomsDao.watchRoomWithStats();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class RoomActions extends _$RoomActions {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<int> createRoom(String name, String iconName) async { ... }
|
||||
Future<void> updateRoom(Room room) async { ... }
|
||||
Future<void> deleteRoom(int roomId) async { ... }
|
||||
Future<void> reorderRooms(List<int> roomIds) async { ... }
|
||||
}
|
||||
```
|
||||
|
||||
3. **lib/features/rooms/presentation/icon_picker_sheet.dart**: Create a bottom sheet widget showing a grid of curated Material Icons from `curatedRoomIcons`. Use a `GridView.count` with crossAxisCount 5. Each icon is an `InkWell` wrapping an `Icon` widget. Selected icon gets a colored circle background using `colorScheme.primaryContainer`. Takes `selectedIconName` and `onIconSelected` callback. Show as modal bottom sheet with `showModalBottomSheet`.
|
||||
|
||||
4. **lib/features/rooms/presentation/room_form_screen.dart**: Full-screen Scaffold form for creating and editing rooms. Constructor takes optional `roomId` (null for create, non-null for edit). Uses `ConsumerStatefulWidget`.
|
||||
|
||||
Form fields:
|
||||
- Name: `TextFormField` with autofocus, validator (non-empty, max 100 chars), German label "Raumname"
|
||||
- Icon: tappable preview showing current selected icon + "Symbol waehlen" label. Tapping opens `IconPickerSheet`. Default to first icon in curated list for new rooms.
|
||||
- For edit mode: load existing room data via `ref.read(appDatabaseProvider).roomsDao.getRoomById(roomId)` in `initState`.
|
||||
- Save button in AppBar (check icon). On save: validate form, call `roomActions.createRoom()` or `roomActions.updateRoom()`, then `context.pop()`.
|
||||
- AppBar title: "Raum erstellen" for new, "Raum bearbeiten" for edit.
|
||||
|
||||
5. **lib/core/router/router.dart**: Add nested routes under the `/rooms` branch:
|
||||
- `/rooms/new` -> `RoomFormScreen()` (create)
|
||||
- `/rooms/:roomId` -> `TaskListScreen(roomId: roomId)` (will be created in Plan 03, use placeholder for now if needed)
|
||||
- `/rooms/:roomId/edit` -> `RoomFormScreen(roomId: roomId)` (edit)
|
||||
- `/rooms/:roomId/tasks/new` -> placeholder (Plan 03)
|
||||
- `/rooms/:roomId/tasks/:taskId` -> placeholder (Plan 03)
|
||||
|
||||
For routes that reference screens from Plan 03 (TaskListScreen, TaskFormScreen), import them. If they don't exist yet, create minimal placeholder files `lib/features/tasks/presentation/task_list_screen.dart` and `lib/features/tasks/presentation/task_form_screen.dart` with a simple `Scaffold(body: Center(child: Text('Coming soon')))` and a `roomId`/`taskId` constructor parameter.
|
||||
|
||||
6. **lib/l10n/app_de.arb**: Add new localization keys for room management:
|
||||
- "roomFormCreateTitle": "Raum erstellen"
|
||||
- "roomFormEditTitle": "Raum bearbeiten"
|
||||
- "roomFormNameLabel": "Raumname"
|
||||
- "roomFormNameHint": "z.B. Kueche, Badezimmer..."
|
||||
- "roomFormNameRequired": "Bitte einen Namen eingeben"
|
||||
- "roomFormIconLabel": "Symbol waehlen"
|
||||
- "roomDeleteConfirmTitle": "Raum loeschen?"
|
||||
- "roomDeleteConfirmMessage": "Der Raum und alle zugehoerigen Aufgaben werden unwiderruflich geloescht."
|
||||
- "roomDeleteConfirmAction": "Loeschen"
|
||||
- "roomCardDueCount": "{count} faellig"
|
||||
- "cancel": "Abbrechen"
|
||||
|
||||
Use proper German umlauts (Unicode escapes in ARB: \u00fc for ue, \u00f6 for oe, \u00e4 for ae, \u00dc for Ue, etc.).
|
||||
|
||||
7. Run `dart run build_runner build --delete-conflicting-outputs` to generate provider .g.dart files.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze lib/features/rooms/presentation/ lib/core/router/router.dart && flutter test</automated>
|
||||
</verify>
|
||||
<done>
|
||||
Room providers connect to DAO layer. Room form screen handles create and edit with validation. Icon picker shows curated grid in bottom sheet. Router has nested room routes. New ARB keys added. All code analyzes clean.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Build rooms screen with reorderable card grid and room card component</name>
|
||||
<files>
|
||||
lib/features/rooms/presentation/rooms_screen.dart,
|
||||
lib/features/rooms/presentation/room_card.dart
|
||||
</files>
|
||||
<action>
|
||||
1. **lib/features/rooms/presentation/room_card.dart**: Create `RoomCard` StatelessWidget accepting `RoomWithStats` data. Per user decision (2-CONTEXT.md):
|
||||
- Card with `InkWell` wrapping, `onTap` navigates to `/rooms/${room.id}` via `context.go()`
|
||||
- Column layout: Icon (36px, using `mapIconName(room.iconName)`), room name (titleSmall), due task count badge if > 0 (bodySmall, using `colorScheme.error` color, text from `roomCardDueCount` ARB key)
|
||||
- Thin cleanliness progress bar at BOTTOM of card: `LinearProgressIndicator` with `minHeight: 3`, value = `cleanlinessRatio`, color interpolated green->yellow->red using `Color.lerp` with coral `Color(0xFFE07A5F)` (overdue) and sage `Color(0xFF7A9A6D)` (clean), backgroundColor = `surfaceContainerHighest`
|
||||
- Use `ValueKey(room.id)` for drag-and-drop compatibility
|
||||
- Card styling: `surfaceContainerLow` background, rounded corners, subtle elevation
|
||||
|
||||
2. **lib/features/rooms/presentation/rooms_screen.dart**: Replace the existing placeholder with a `ConsumerWidget` that:
|
||||
- Watches `roomWithStatsListProvider` for reactive room data
|
||||
- Shows the Phase 1 empty state (icon + text + "Raum erstellen" button) when list is empty. The button navigates to `/rooms/new`
|
||||
- When rooms exist, shows a `ReorderableBuilder` from `flutter_reorderable_grid_view` wrapping a `GridView.count(crossAxisCount: 2)` of `RoomCard` widgets
|
||||
- Each card has `ValueKey(roomWithStats.room.id)`
|
||||
- On reorder complete, calls `roomActions.reorderRooms()` with the new ID order
|
||||
- FAB (FloatingActionButton) at bottom-right with `Icons.add` to navigate to `/rooms/new`
|
||||
- Uses `AsyncValue.when(data: ..., loading: ..., error: ...)` pattern for the stream provider
|
||||
- Loading state: `CircularProgressIndicator` centered
|
||||
- Error state: error message with retry option
|
||||
|
||||
Add a `PopupMenuButton` or long-press menu on each card for edit and delete actions:
|
||||
- Edit: navigates to `/rooms/${room.id}/edit`
|
||||
- Delete: shows confirmation dialog per user decision. Dialog title from `roomDeleteConfirmTitle`, message from `roomDeleteConfirmMessage` (warns about cascade), cancel and delete buttons. On confirm, calls `roomActions.deleteRoom(roomId)`.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze lib/features/rooms/presentation/ && flutter test</automated>
|
||||
</verify>
|
||||
<done>
|
||||
Rooms screen displays 2-column reorderable card grid with room icon, name, due task count, and cleanliness bar. Empty state shows when no rooms. FAB creates new room. Cards navigate to room detail. Long-press/popup menu offers edit and delete with confirmation dialog. Drag-and-drop reorder persists via DAO. All existing tests still pass.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
```bash
|
||||
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze && flutter test
|
||||
```
|
||||
All code analyzes clean. All tests pass. Room creation, editing, deletion, and reorder are functional at the UI level.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Rooms screen shows 2-column card grid (or empty state)
|
||||
- Room cards display: icon, name, due task count, thin cleanliness bar (green->yellow->red)
|
||||
- Room form: create and edit with name field + icon picker bottom sheet
|
||||
- Icon picker: curated grid of ~25 household Material Icons in bottom sheet
|
||||
- Delete: confirmation dialog with cascade warning
|
||||
- Drag-and-drop: reorder persists to database
|
||||
- FAB for creating new rooms
|
||||
- Router: nested routes /rooms/new, /rooms/:roomId, /rooms/:roomId/edit
|
||||
- Localization: all room UI strings from ARB
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-rooms-and-tasks/02-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user