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