# Daily Zone Check-in Workstream 16 — campus staff log a daily self-regulation "zone" (Zones of Regulation: blue/green/yellow/red). History is retained per day, and an eligible user who has not checked in today is nudged on the dashboard, the `/zones-of-regulation` page, and in the notification dropdown. ## "Today" is server-computed in the campus timezone The check-in date is **not** decided by the client. Each campus carries a required IANA `timezone` (`campuses.timezone`); the service computes the campus-local date (`localDateInTimezone`, native `Intl`, DST-correct) so "today" is independent of the caller's device clock/zone and correct across organizations, campuses, and timezones. ## Storage (reuses `user_progress`) No new table. A check-in is a `user_progress` row with `progress_type = zone_checkin` and `item_id` = the campus-local date (`YYYY-MM-DD`), `value` = the zone color. Because the per-user upsert key is `(userId, progress_type, item_id)`, this yields **one row per user per day**, and the set of rows is the history (`item_id` sorts chronologically). The thin `services/zone-checkin.ts` wraps `UserProgressService` and owns the timezone/date logic, keeping the generic `user_progress` endpoint generic. ## Routes (`/api/zone_checkins`) All require `ZONE_CHECKIN` (the four campus staff roles). - `GET /today` → `{ date, zone, isCheckedInToday }` (campus-local date). - `POST /` → record today's zone. Body `{ data: { zone } }` (blue|green|yellow|red). - `DELETE /today` → clear today's check-in. - `GET /?from=&to=` → the caller's history (`{ rows: [{ date, zone }], count }`). ## Authorization - `ZONE_CHECKIN` — `director` (full access), `office_manager` (via `...MODULE_ACTIONS`), `teacher`, `support_staff` (explicit grants). Other roles (owner/superintendent/student/guardian/system) are not granted it; the frontend also gates the nudge to the four campus roles (`canZoneCheckIn`). Reads/writes are scoped to the caller's own `userId` by `UserProgressService`. - A user with no campus has no campus-local "today" — the service rejects with a validation error (only campus staff reach these routes). ## Tests - **Unit** (`npm test`): `shared/constants/timezone.test.ts` (campus-local date incl. Phoenix no-DST + a DST zone; `isValidIanaTimezone`) and `shared/constants/zone-checkin.test.ts` (`isZoneCheckinColor`). - **Frontend unit** (`vitest`): `business/zone-checkin/selectors.test.ts` (eligibility + nudge) and the top-bar notification builder (incl. the zones `href`). - **Seeded e2e** (`frontend/tests/e2e/zone-checkins.seeded.e2e.ts`, `npm run test:e2e:content`): a campus-staff record/read-back/clear of today's zone, invalid-zone rejection, and external-role lockout. ## Open / deferred - A manager-facing aggregate (campus self-regulation trends across staff) would need a cross-user report endpoint (`user_progress` is self-scoped) — deferred. - Editing a campus `timezone` is part of the (design-gated) campus admin UI; for now it is seeded and validated at the API.