# FamilyDashboard moving status timeline patterns

Use this when refining the Home/Todo child status timeline in FamilyDashboard.

## Core UX model

- Keep the horizontal timeline, but for the selected day = today use a **fixed-now / moving-world** model.
- The now cursor stays visually fixed around 24% from the left.
- Blocks move relative to now: little past on the left, relevant future on the right.
- Do not revive the old `moving-fill`/sheen progress bar for today; use a calm `timeline-now-fixed` cursor and a quiet track.
- For non-today dates, a static full-day viewport is acceptable.

## Backend status logic

Add/maintain a general `get_next_departure_event(child, date, now)` concept rather than only school departure:

1. Afternoon school:
   - target = Schule
   - target_start = first school block after lunch
   - travel_minutes = `ChildSettings.commute_minutes` (default 15)
   - leave_time = target_start - travel_minutes
2. External activities:
   - `counts_as_school=false`
   - `affects_timeline=true`
   - block type in `external_activity`, `music`, `sports`, or relevant `other`
   - travel default 10 minutes until a per-block travel field exists
3. Pick the earliest upcoming leave/target combination. If an external activity comes before afternoon school, external wins.
4. Only show true `afternoon_free` when there is no relevant upcoming school/external departure and no current external activity.

Expose optional departure fields in `/api/dashboard` per child:

```json
{
  "nextDepartureTime": "12:40",
  "nextDepartureTarget": "Gitarre",
  "nextDepartureType": "external_activity",
  "nextDepartureIcon": "🎸",
  "minutesUntilNextDeparture": 30
}
```

## Lunch examples to preserve

### External activity after lunch

For Emilia-like Tuesday 12:10 with Gitarre 12:50–13:15:

- status: `dayPhase = lunch_break`
- primary: `Loslaufen in 30 Min.`
- secondary: `zur Gitarre`
- timeline:
  - `lunch` 11:50–12:30 `Mittagessen` icon `🍽️`
  - `prepare_external` 12:30–12:40 `Zähneputzen / Schuhe` icon `🪥`
  - `leave_external` 12:40 `Loslaufen` icon `🚶`
  - named external range, e.g. `Gitarre ZI 205` 12:50–13:15 icon `🎸`, `emphasis=external`
  - `Frei` after the activity, icon `🌿`
  - `Nachtessen` 18:00 as future/far target, icon `🍽️`

During the external activity, show e.g. `Gitarre läuft bis 13:15`; after it, show `Nachmittag frei` and keep dinner/far target visible in the timeline.

### Afternoon school after lunch

For Valerie-like Tuesday 12:10:

- `Mittagessen` 11:50–13:00 icon `🍽️`
- `Zähneputzen / Schuhe` 13:00–13:15 icon `🪥`
- `Loslaufen` 13:15 icon `🚶`
- `Schule` 13:30–school end icon `🏫`
- Do not insert a tiny generic `Pause/Frei` block between lunch and preparation unless there is real meaningful time.

## Frontend rendering rules

- `TimelineStep` should include optional `icon?: string | null`.
- Backend should send icons, but frontend should keep a fallback mapping:
  - Gitarre `🎸`, Klavier `🎹`, Mädchenriege/Turnen `🤸`, Schule `🏫`, Loslaufen/Ablaufen `🚶`, Zähne `🪥`, Schuhe `👟`, Mittag/Nachtessen/Essen `🍽️`, Frei `🌿`, Schlafen `🌙`, Ritual `📖`, Heim/Ankunft `🏠`.
- Implement/keep display modes based on percent width:
  - `<4%`: `dotOnly`
  - `<12%`: `iconOnly`
  - `<18%`: `compact`
  - otherwise: `full`
- Every meaningful time window should have an icon. If text does not fit, show icon only. Do not render clipped nonsense labels.
- Milestones such as `Loslaufen`, `Schule`, and named external activities should be recognizable by icon even when text is hidden.
- Keep title/detail panels clickable so full text remains available even when ranges use compact/icon-only mode.

## CSS targets

- Track can be ~96–112px high for iPad legibility.
- Range cards can be ~58–66px high.
- Timeline range icons ~24–28px; marker icons ~26–30px.
- Text should be dark and high contrast; avoid tiny pills.
- The fixed-now cursor must be clear but not cover important labels.

## Verification checklist

Run backend timeline tests plus frontend TypeScript/build:

```bash
pytest backend/tests/test_dashboard_lunch_modes.py -q
docker compose exec -T frontend npm run build
```

Smoke API cases with injected `now` in tests or direct service calls:

- Emilia-like 12:10: `lunch_break`, `Loslaufen in ...`, target Gitarre, `🎸`, free after event, dinner visible.
- Emilia-like after external event: `afternoon_free`, free block starts after event, dinner visible.
- Valerie-like 12:10: lunch to 13:00, teeth/prep to 13:15, walk at 13:15, school at 13:30.
- Check no unwanted `Hausaufgaben` or generic `Pause` block in tight lunch windows.
- Existing morning/evening tests stay green.

## Deploy reminder

For this project the rebuild command is:

```bash
/home/agent/bin/rebuild-familydashboard
```

The script may return a transient frontend `curl` connection reset immediately after container start. Do a retry health check before declaring deploy failed:

```bash
curl -fsS http://localhost:8000/api/health
curl -I http://localhost:5173
```
