docs(08-task-delete): create phase plan
This commit is contained in:
290
.planning/phases/08-task-delete/08-02-PLAN.md
Normal file
290
.planning/phases/08-task-delete/08-02-PLAN.md
Normal 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>
|
||||
Reference in New Issue
Block a user