4.3 KiB
Daily Zone Check-in
Workstream 16 — 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). Users
without a campus use UTC for the daily key, so organization- and school-scope
staff can still complete the same daily workflow.
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)
Personal routes require explicit ZONE_CHECKIN; globalAccess alone does not
imply this workflow permission. The scoped completion route requires
READ_ZONE_CHECKIN_REPORTS.
GET /today→{ date, zone, isCheckedInToday }(campus-local date).POST /→ record today's zone. Body{ data: { zone } }(blue|green|yellow|red). Parent-scope users acting through a drilled child scope receive403 Forbiddeninstead of creating reportable rows in the child scope.DELETE /today→ clear today's check-in.GET /?from=&to=→ the caller's history ({ rows: [{ date, zone }], count }).GET /completion→ scoped staff report for leaders withREAD_ZONE_CHECKIN_REPORTS; returns{ summary, rows }for today's server computed dates per staff member.
Authorization
ZONE_CHECKINis seeded for staff roles that must complete the daily workflow, including organization, school, campus, and class scope staff (owner,superintendent,principal,registrar,director,office_manager,teacher, andsupport_staff). Users can receive or lose it only through effective permissions (custom_permissions/custom_permissions_filter). The frontend and backend both gate this workflow by permission, not by role name. Reads/writes are scoped to the caller's ownuserIdbyUserProgressService.READ_ZONE_CHECKIN_REPORTSis seeded for scope leaders and report readers. The completion report applies the caller's effective organization/school/campus/class scope and includes pending rows for staff without today's check-in.- Check-ins persist only when the active scope is the user's own scope. Parent users drilled into a
child school/campus/classroom do not see the personal check-in UI there, and the backend does not
create reportable
user_progressrows for that child scope. - Users with no campus use UTC for "today"; campus users use their campus timezone.
Tests
- Unit (
npm test):shared/constants/timezone.test.ts(campus-local date incl. Phoenix no-DST + a DST zone;isValidIanaTimezone) andshared/constants/zone-checkin.test.ts(isZoneCheckinColor). - Frontend unit (
vitest):business/zone-checkin/selectors.test.ts(eligibility + nudge), top-bar notification builder (incl. the zoneshref), profile unified result rows, and director-dashboard unified result/risk rows. - 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.
Content
Regulation zone cards and page copy are organization-preset content catalog
records (regulation-zones and zones-of-regulation-page-content). New
organizations receive those records from the content-catalog seeder/backfill; the
daily check-in result history remains in user_progress.
Existing deployments receive report grants through
20260619070000-grant-zone-checkin-report-permission.ts and the registrar
personal workflow grant through
20260619071000-grant-registrar-zone-checkin-permission.ts; both migrations are
idempotent.