09 — User Behaviour (Teachers Module)
- 1. Global behaviours
- 2. List screen (S1)
- 3. Detail screen (S2–S4)
- 4. Create/Edit forms (S5/S6)
- 5. Assignment editor (S7)
- 6. Deactivate (S8)
- 7. Teacher self (S9/S10,
(planned)) - 8. Error copy table (module)
- 9. Behavioural edge cases (derived)
Rules of engagement: what the UI may/must not do, derived strictly from backend behaviour. Contradictions with
PLAN.md/ flow docs are flagged, not resolved by invention.
1. Global behaviours
| Rule | Source |
|---|---|
Every list is tenant-scoped automatically; client never sends tenantId | base.repository.ts:20-30,33-35 |
Every request carries Bearer JWT; 401 → single-flight refresh → sessionExpired | 00-shared/06 §3.6 |
| Deleted (soft) records are invisible to the client — 404, not a flag in payload | base.repository.ts:20-30 |
success:false never renders raw message for 5xx — generic + requestId; 4xx business text allowed | 00-shared/07 §11 |
| Server returns raw ObjectIds for refs — client joins names from catalogs; missing ref = "—" | teacher.schema.ts:22-26,44-48 |
2. List screen (S1)
- Search input debounced 300 ms (
AppSearchBar); sinceqis ignored server-side (teacher.service.ts:66-78), client filters loaded pages locally and shows "Searching across loaded page only" helper when filter active + more pages exist (OQ-3). - Filter chips re-run local filter on loaded items; server-side filters don't exist — never claim otherwise in copy.
- Pagination: infinite scroll;
meta.hasNextstops loader (pagination-query.dto.ts:41-55). - Pull-to-refresh resets page 1 + bypasses cache (00-shared/06 §3.3).
- Empty states per 04 §7.
3. Detail screen (S2–S4)
- Tabs keep state; switching year in Assignments refetches matrix (server keyed by
academicYearId—subject-assignment.service.ts:26-31). - If detail fetch 404 → user deleted concurrently → leave detail, snackbar "Teacher removed".
- Edit navigates to S6 with prefill; back from form → refresh detail.
4. Create/Edit forms (S5/S6)
- Never optimistic — create/update are server-confirmed writes (00-shared/06 §3.5).
- On 409: keep form values; show
AppBanner+ focus related field. - On 400: map
details[].field→ inline field errors; focus first invalid (09_Accessibility_Baseline.md §10). - On 5xx/network: keep form values,
AppErrorStateinline with Retry (idempotent because create duplicate → 409 guard, update → idempotent$set). - Offline: form entry blocked with
AppOfflineBanner(no offline queue for teachers, 00-shared/06 §3.7).
5. Assignment editor (S7)
- Add = server-confirmed (no optimistic row).
- Client duplicate check runs on picker selection AND on submit (race between two admins possible — server accepts both; see QA-3).
- Remove: confirm dialog (destructive) → server 200 → row removed; 404 → row already gone → remove locally + snackbar.
- Year switch in S3 discards in-progress editor (sheet closes).
6. Deactivate (S8)
- Server-confirmed, never optimistic (irreversible).
- Confirm button pending state; haptic
heavyImpact(); on 200 → back + snackbar. - No guard server-side on active assignments/timetable (OQ-5) — dialog warns: "Existing assignments and schedule entries are not modified."
7. Teacher self (S9/S10, (planned))
- Read-only; no create/edit/remove affordances for
teacherrole. - On
on_leave/inactive: banner on My Teaching; schedule still visible. - No server "me" endpoint — client resolves own teacher id via stored session mapping (see OQ-2; documented, not invented).
8. Error copy table (module)
| Code | Screen | Client message (i18n key) |
|---|---|---|
| 409 user | F1 | teachers.create.conflict.user — "A teacher profile already exists for this user." |
| 409 emp | F1 | teachers.create.conflict.employeeNumber — "Employee number already exists." |
| 404 | S2/S3/S8 | teachers.notFound — "Teacher not found or removed." |
| 404 assignment | S3 remove | assignments.notFound — "Assignment already removed." |
| 400 | F1/F2/F3 | per-field from details |
| 429 | all | errors.rateLimited — "Too many requests. Try again in a moment." |
| 5xx | all | errors.server + requestId |
9. Behavioural edge cases (derived)
POST /teacherswithuserIdalready having a profile → 409 (create path only; update cannot change userId).PATCHemployeeNumber→ no uniqueness 409 (only create checks;teacher.service.ts:32-38vs80-93) — client pre-check is best-effort.- Assignment lists keyed by
academicYearIdreturn [] when param missing (subject-assignment.service.ts:26-31) — client always sends it. - Timetable by teacher id returns entries sorted
dayOfWeek, startTime(timetable.service.ts:55-60) — client must not re-sort by date.