# FamilyDashboard task template deactivation and dashboard visibility

Use this when fixing FamilyDashboard task-template deletion/deactivation, dashboard task visibility, seed tasks, or day-close penalties.

## Durable lesson
Soft-deleting a `TaskTemplate` is not enough. Existing `TaskInstance` rows can remain `open` and continue to appear on Home/Todo or receive day-close penalties unless the deactivation path and read paths handle them explicitly.

## Required backend pattern
1. Centralize deactivation in a helper such as:
   - `deactivate_task_template(session, template, today)`
   - set `template.active = False`
   - cancel only `TaskInstance.status == open` rows with `task_date >= today`
   - never alter `done` instances
   - never alter/delete old `CoinTransaction` rows
2. Use the helper from both:
   - `DELETE /api/admin/task-templates/{template_id}`
   - `PATCH /api/admin/task-templates/{template_id}` when `active` changes from true to false
3. Return a response with the affected count, e.g.:
   - `{ ok: true, task_template: ..., cancelled_instances: N }`

## Dashboard visibility rule
When building Home/Todo tasks:
- load instances for child/date
- load referenced templates in one query
- hide `TaskStatus.cancelled`
- hide instances whose `task_template_id` points to an inactive template
- keep template-less instances visible (study/manual/quiz/other generated tasks)

Pseudo-pattern:
```python
template_ids = {i.task_template_id for i in instances if i.task_template_id is not None}
templates = session.exec(select(TaskTemplate).where(TaskTemplate.id.in_(template_ids))).all() if template_ids else []
template_active = {t.id: t.active for t in templates}

def is_visible_task(instance):
    if instance.status == TaskStatus.cancelled:
        return False
    if instance.task_template_id is None:
        return True
    return template_active.get(instance.task_template_id, True)
```

## Day-close penalties
`finalize_day()` must ignore:
- `cancelled` tasks
- open required tasks whose template is now inactive

This prevents future penalties after a parent disables a formerly required task.

## Seed/migration pitfall
If a routine task is replaced by a Timeline block (e.g. Zähneputzen), remove it from default seeds and add an idempotent startup migration that deactivates matching existing templates and cancels open today/future instances. Match specific toothbrushing variants only (`zaehne`, `zähne`, `zahnputzen`, `zähneputzen`), not broad hygiene tasks.

## Admin UX pattern
- Keep **Deaktivieren** and **Löschen** distinct when the user asks for both.
- **Deaktivieren** is reversible: use `PATCH active=false`, hide inactive templates by default, provide **Inaktive anzeigen** and **Reaktivieren**, and show feedback using the backend count: `Aufgabe deaktiviert · N offene Tagesaufgaben ausgeblendet`.
- **Löschen** is an explicit destructive UI action: call the delete endpoint, hide the template from normal Admin lists, preserve ledger/history, and prevent seed recreation with a tombstone marker. See `familydashboard-admin-task-actions-layout.md` for the full pattern.
- Invalidate admin and dashboard queries after deactivation/reactivation/delete.

## Regression tests to add
- deactivating a template hides an already-created today instance from dashboard
- DELETE cancels open current/future instances
- done instances remain done and existing ledger transactions remain untouched
- day close ignores inactive-template tasks
- seed does not reactivate Timeline-replaced toothbrushing tasks

## Calendar side polish from same class of UI issue
For calendar chips/titles, enforce wrapping within cards/dialogs:
```css
.calendar-day,
.calendar-chip-stack,
.calendar-chip {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
}
.calendar-chip,
.calendar-chip small {
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.calendar-event-edit-trigger,
.calendar-event-row {
  min-width: 0;
  max-width: 100%;
}
.calendar-event-edit-trigger .calendar-chip { width: 100%; }
```
