# Phase 5 — the per-child dashboard

Status: **done.** Sam can see, per child and on a phone, what is overdue, what is
due today, what is due this week, and the messages. See AUD-109.

## What the dashboard shows

One tab per child (Harry, Finn). The selected tab shows, in order (FR-7):

1. **Overdue** — overdue first, with a red marker.
2. **Due today**.
3. **Due this week** — the next seven days.
4. **No due date** — only when the child has such tasks.
5. **Messages** — the Compass news feed, newest first.

A task is **overdue** when Compass says so (`Status: Overdue` from the JSON
service, `Submitted: Overdue` from the grid), or when it is not submitted and its
due date is before today. **Submitted** work (On Time, Received Late) is not
"due", so it stays out of the lists; the count is shown in the panel footer.

A **calendar event** is never overdue: a past lesson is history, not work to do.
Events appear under due today and due this week.

The page is server-rendered HTML with HTMX. It is phone-first: one column, 44px
touch targets, and the Delete button steps below the title under 520px.

## Routes

| Method | Path | Shows |
|---|---|---|
| `GET` | `/?child=<id>` | the per-child dashboard (first child when `child` is absent) |
| `GET` | `/items/{id}` | item detail: subject, teacher, due date, link, full text (§5 screen 3) |
| `POST` | `/items/{id}/delete` | the Delete action on an item (FR-10) |
| `POST` | `/summaries/build` | build a draft brief for one child (FR-10 summary half) |
| `POST` | `/summaries/{id}/delete` | the Delete action on a stored brief (FR-10) |
| `GET` | `/partials/scan-status` | the scan banner fragment (polled every 60s) |

Every `POST` checks the session CSRF token. An HTMX request gets `204 No
Content`, so the row is removed in place; a plain form post gets `303 See Other`
back to the dashboard, so the page works without JavaScript.

A deleted item returns on the next parse if the school still lists it. A deleted
brief is gone.

## Fail loudly (FR-12)

The banner reads the scan log. It is never an empty day:

- **NO SCAN HAS RUN YET** — amber. A child with no scan shows
  "Unknown — the last scan did not complete." instead of "Nothing overdue.".
- **SCAN FAILED** — red, with the recorded error. The child panel repeats the
  warning.
- **Last scan completed** — green. Only then may an empty section say
  "Nothing overdue.".

The banner is per child (`ScanSummaryForChild`), so a failed scan for Finn is
never hidden by a successful scan for Harry.

## Where the items come from

No new scan code: Phase 3/4 store the items. Phase 5 adds the reads and the
classification (`internal/dashboard`).

- `internal/dashboard/dashboard.go` — classify items into buckets; render the
  plain-text brief.
- `internal/store/dashboard.go` — `ItemsForChild`, `ItemByID`, `DeleteItemByID`,
  `ScanSummaryForChild`, and the summary create/list/get/delete helpers.
- `internal/web/server.go` — the routes and handlers.
- `internal/web/templates/dashboard.html`, `item.html` — the screens.

## The stored brief (FR-10, delete half)

A brief is a row in `notifications` (AGENTS.md §7). `sent_at` stays NULL for a
draft. Building a brief stores the text; **sending** it is Phase 7.

```bash
docker exec compass-teams-nhs /app/school brief build --child Harry
docker exec compass-teams-nhs /app/school brief list
```

## Verification (2026-10-15, live)

```bash
make test    # go test ./...
```

The tests cover the buckets, the brief text, the stored brief round-trip, and the
HTTP routes. `internal/web/server_test.go` logs in and proves:

- the selected child's items render and the other child's do not,
- the overdue item renders before the due-today item,
- the item detail carries subject, teacher and body,
- delete removes the row and a tokenless delete is refused (403),
- the banner shows `NO SCAN HAS RUN YET`, then `SCAN FAILED` with the error, then
  `Last scan completed`, and only a healthy scan allows "Nothing overdue.".

Live counts on `.13` (Compass data of 2026-10-15):

| Child | Overdue | Due today | This week | No due date | Messages |
|---|---|---|---|---|---|
| Harry | 6 | 8 | 7 | 18 | 1 |
| Finn | 2 | 0 | 0 | 8 | 1 |

The overdue counts match the home-page reconciliation from Phase 4a (Harry 6,
Finn 2).

Phone-width screenshots (390 × 844) are attached to AUD-109:
`scripts/screenshot-phase5.mjs`.

## Known gaps

- Teams (FR-15) is a separate source; the dashboard shows Compass only.
- Sending a brief is Phase 7. Phase 5 stores and deletes a draft.
- The calendar write (Phase 6) and the Hub API (Phase 8) are not built yet.
