# 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=` | 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.