09 — User Behaviour (Attendance Module)
- 1. Fast-marking patterns
- 2. Mistakes & recovery
- 3. Conflict semantics (must-read)
- 4. Notification-driven behaviour (parents)
- 5. Data-entry ergonomics (physical)
- 6. Behaviour rules summary
How teachers actually use the marking surface, the mistakes they make, and what the UI must do about each. All corrective behaviour maps to backend semantics — most importantly the upsert-overwrite model (
attendance.repository.ts:23-29) and the one-doc-per-student-per-day invariant (attendance.schema.ts:59-62).
1. Fast-marking patterns
| Pattern | Frequency driver | UI support |
|---|---|---|
| Mark-by-exception (assume present) | 90%+ of classes | Default-present chips (dashed = unsaved); only changed rows send requests |
| Sweep all absent | surprise absences (sports day, late bus) | FAB "Mark all absent" → bulk confirm → UndoBar |
| Tap-cycle to correct | walk-by marking | 1/2/3-tap cycle Present→Absent→Late→Present (06 §2) |
| Filter to "unmarked" | end-of-class sweep | StatusFilterChips with counts |
| Mark-late for stragglers | late arrivals | popover: late + checkIn=now (06 §4) |
| Batch for a subgroup | lab sessions, early dismissals | long-press multi-select → BatchEditSheet |
The 60-second rule: the design goal — an entire 40-student class marked in ≤ 60 s. Anything requiring a dialog per student violates it; the only blocking dialog is the overwrite-conflict banner (deliberate, because re-marks silently change data).
2. Mistakes & recovery
2.1 Wrong date
- What happens: teacher marks today but means yesterday; or opens yesterday to correct and accidentally marks today.
- System behaviour: each day is independent (docs keyed by
date). Nothing merges; wrong-day marks persist as separate docs. - UI:
- Date is pinned in the AppBar subtitle and the grid header ("Mon, 20 Jul 2026") —
- mark requests embed the pinned date; the chip shows the date in the popover title.
- A "Marked for Mon 20 Jul" confirmation snackbar on first mark of the session (once per date-switch, not per mark).
- Correcting: teacher switches date → marks correct → the wrong-day record is found in
S4 day view → PATCH status to
present/holidayas appropriate (no delete endpoint exists — OQ-11: noDELETE /attendance/:idinattendance.controller.ts; a "wrong day" fix = setholidayor remark, flagged for the product owner).
2.2 Wrong status (mistap)
- What happens: tap-cycling overshoots (present→absent→late when only absent was meant), or taps the wrong row.
- System behaviour: single-tap writes immediately (upsert). No server-side undo.
- UI:
- The 3-state cycle makes overshoot recoverable in one more tap (back to intended).
UndoBar(4 s) after every mark: reverts by re-sending the previous status (safe — upsert overwrite).- Haptics
lightImpacton save confirm the tap landed on the right row.
2.3 Wrong student (row confusion)
- What happens: two similar names / misaligned rows on a long grid.
- UI: rows alternate tint (zebra,
surfaceVariant4% alpha) only when > 30 students; name istitleMedium+rollNumber(mono,student.schema.ts:23-25); popover title always shows full name + roll no; filters collapse the list so targets are adjacent.
2.4 Marking the same student in two classes (same day)
- What happens: student attends class A period 1 and class B period 2; teacher B marks
them; unique index
{tenantId, studentId, date}(attendance.schema.ts:59-62) permits one doc per day — remarking from class B overwrites class A's record and keeps class A'sclassId(attendance.repository.ts:23-33). - UI: first grid session of the day shows a one-time inline hint on rows already marked today: chip shows existing status + tooltip "Already marked in Class A — this will overwrite". The overwrite-conflict banner (§3) covers the multi-select case.
2.5 Typos in remarks / check-in times
- What happens: 09:15 vs 09:50; wrong free-text note.
- UI: popover shows existing values ("currently 09:12"); PATCH flow (S7/S4) edits in place. Client-side numeric/time formatting; remarks free text ≤ 500 chars.
3. Conflict semantics (must-read)
| Situation | Server truth | UI |
|---|---|---|
| Remark same status | no-op upsert (same values re-saved, attendance.repository.ts:25-27) | chip flashes, no banner |
| Remark different status, single row | overwrite | chip flips + "was: absent" transient label |
| Batch containing already-marked rows | overwrite all in one bulk | amber banner "N already marked — will be overwritten"; CTA "Overwrite N" (06 §3) |
| Offline queue collides with newer online data on flush | last-write-wins per student/date (upsert) | flush preview lists collisions; user confirms or drops stale rows |
| Two devices/teachers race | unique index: one doc; both upserts, last write wins | rare; grid refresh on 409 |
4. Notification-driven behaviour (parents)
- Alert arrival (when
(planned)dispatch lands): parent taps →studylyon://attendance/:date→ S7 day view → sees status + source (manual/biometric,attendance.schema.ts:16-21). - Copy honesty: "3 absences this month" (worker counts all absent docs, no window —
attendance.worker.ts:70-74; OQ-1). Never "consecutive".
5. Data-entry ergonomics (physical)
- One-hand tablet landscape: chips right side, thumb zone.
- Landscape keyboard on desktop: arrows navigate rows,
P/A/L/H/V/Dset status on focused row,Enteropens popover,Escexits selection/modes. - Sticky header (MarkedCountBar + filters) so "done?" is always visible mid-scroll.
- Auto-scroll to next unmarked row after filter-to-unmarked marking (proposed).
6. Behaviour rules summary
- Never block single-row marking with a dialog (exception: overwrite-conflict in batch).
- Every write is instantly visible in chip + MarkedCountBar (optimistic + pending indicator, 13_State_Management.md §5).
- Every failure is recoverable: snackbar retry; offline queue flush; undo where safe.
- Time/date shown in tenant-local time; wire values always
YYYY-MM-DD/ ISO-8601. - Honest about pipeline gaps (biometric "captured, not applied", alert "planned").