Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

09 — User Behaviour (Timetable Module)

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

RuleSource
Every list is tenant-scoped automatically; client never sends tenantIdbase.repository.ts:20-30,33-35
Every request carries Bearer JWT; 401 → single-flight refresh → sessionExpired00-shared/06 §3.6
Soft-deleted records are invisible — 404, not a flag in payloadbase.repository.ts:20-30
Server returns raw ObjectIds for refs — client joins names from catalogs; missing ref = "—"timetable.schema.ts:18-40
success:false never renders raw message for 5xx — generic + requestId; 4xx business text allowed00-shared/07 §11

2. Grid screens (S1/S2/S3)

  • Never re-sort: GET /timetable?classId= / ?teacherId= return entries sorted dayOfWeek, startTime (timetable.service.ts:48-60); the grid maps them to cells directly.
  • One request per scope; scope switch (class ↔ teacher) re-fetches — no merged single-request option exists (timetable.controller.ts:22-25).
  • Both params sent → classId wins (timetable.controller.ts:26-27); neither → [] (:28) — the client always sends exactly one.
  • Week navigation is a local re-filter (no server "week" or date dimension; timetable.schema.ts:30-37).
  • Room view (S3) merges multiple class responses client-side — never claim a server roomId filter exists (OQ-4; (planned)).

3. Entry editor (S4)

  • Never optimistic — create is server-confirmed (00-shared/06 §3.5).
  • On 409: keep form values; ConflictBanner; focus the conflicting picker.
  • On 400: map details[].field → inline field errors; focus first invalid (09_Accessibility_Baseline.md §10).
  • On 5xx/network: keep form values, AppErrorState inline with Retry.
  • Offline: form entry blocked with AppOfflineBanner (no offline write queue, 00-shared/06 §3.7).
  • Pre-flight warnings are advisory only — the server is authoritative; two admins submitting the same slot both get a 409 for the second one, never corruption.

4. Conflict rules (derived from the server rule)

  • The server checks teacher and room clashes on the same dayOfWeek with overlapping [startTime, endTime) intervals (timetable.service.ts:17-22,62-69).
  • endTime == startTime of another entry → no conflict (back-to-back allowed, :68).
  • Room-less entries only clash on teacher (:20 — undefined clause stripped).
  • Same-class overlaps are not detected (OQ-1) — the UI may highlight them in the grid (client-derived) but must not block or claim server support.

5. Drag & drop / edit semantics (desktop, (planned)-aware)

  • Dropping a slot onto a new cell opens the editor prefilled (a create — no PATCH exists, 01 §10). The original entry is untouched until a delete surface exists; UI copy must be explicit ("This creates a new slot — the original stays until you delete it (planned)").
  • No optimistic move animations that imply persistence.

6. Teacher self (read-only)

  • Read-only grid; no FAB/editor affordances (02 §3).
  • Teacher id for "me": client-resolved (no /timetable/me; teachers module has the same gap, teachers 03 §5).

7. Error copy table (module)

CodeScreenClient message (i18n key)
409 conflictS4timetable.conflict.teacher / timetable.conflict.room — "Teacher already booked {day} {start}–{end}" / "Room already in use…" (generic server message mapped via pre-flight data)
400S4per-field from details
404 (catalog refs missing)gridslot shows "—", no error screen
429allerrors.rateLimited — "Too many requests. Try again in a moment."
5xxallerrors.server + requestId

8. Behavioural edge cases (derived)

  • GET /timetable with no params → [] (timetable.controller.ts:28) — a bare "all entries" fetch is not supported; client must always scope.
  • Non-zero-padded time strings would break conflict math (timetable.service.ts:68) — client picker-only input prevents it (08 F1).
  • "sunday" rejected by @IsEnum (create-timetable-entry.dto.ts:23-25) — no Sunday column exists anywhere in the UI.
  • TimetableEntryCreated is emitted (timetable.service.ts:33-44) but no queue routes it — the UI must not promise notifications/audit from timetable events (01 §6).
  • Cross-tenant entry id in a URL → 404 (scoped find, base.repository.ts:20-30) — treat as removed, never leak existence (00-shared/07 §3).