09 — User Behaviour (Results Module)
- B1 — Marks entry is incremental and re-entrant
- B2 — Client-side guardrails beat server round trips
- B3 — Missing marks read as zero on the report card
- B4 — Publish is a point of no return
- B5 — Publication informs, asynchronously
- B6 — The results list is a flat feed
- B7 — Filters are local until the server supports them
- B8 — Error recovery is code-driven, not message-driven
- B9 — Offline-first grading
- B10 — Behavioural metrics (client instrumentation
(proposed))
Behavioural requirements derived from server semantics. Each behaviour cites the code fact that justifies it. Reference 00-shared/08_Interaction_&_Motion.md for motion defaults and 00-shared/09_Accessibility_Baseline.md for a11y obligations.
B1 — Marks entry is incremental and re-entrant
- Teachers type marks row-by-row; each committed row is an independent upsert
(
examination.service.ts:147-175). The UI must not block other rows on one failing save (per-row saving state, S1 §3). - Re-entry must be frictionless: the same row can be re-submitted any number of
times (update path
:152-161). No "already entered" blocking.
B2 — Client-side guardrails beat server round trips
- Over-max and negative values are caught client-side (08_Form_Specifications.md F1.2)
because the server's rejection (404,
examination.service.ts:145-146) is semantically wrong for the user. Users expect "try again", not "not found".
B3 — Missing marks read as zero on the report card
result.service.ts:70fillsmarksObtained: 0and totals still include the subject maximum (:72). Behaviour rule: never silently show a plausible zero — S4 marks the "not entered" row (hint chip) and S1 shows empty rows, so users can tell "0 scored" (has a row) from "not graded" (no row). Server cannot distinguish.
B4 — Publish is a point of no return
publishResults(examination.service.ts:203-220) is the only visibility switch; no unpublish endpoint exists. The confirm dialog must state this. After publish, S5 disables the action; S1 rows show locked-but-editable state (server still accepts writes —(planned)lock).
B5 — Publication informs, asynchronously
- The visible effect of publish is the
ExamResultsPublishedevent →in-appqueue →results-publishedjob (event-queue-map.ts:27). Users should be told "notification queued", not "notification sent" — delivery is async.
B6 — The results list is a flat feed
GET /api/v1/results/student/:studentIdreturns flatexamination_resultsrows (examination-result.repository.ts:26-28). Users expect exam-grouped results; the client groups (S3) and must tolerate rows whose exam lookup 404s (exam soft-deleted —examination.repository.tsscoped soft-delete) by dropping the orphan group silently.
B7 — Filters are local until the server supports them
- Filter chips on S3/S5 (published-only, grade, class) are client-side. A user
filtering "published only" must not assume server-side security —
publishedAtis never filtered on read paths (examination-result.repository.ts:20-40,(planned)).
B8 — Error recovery is code-driven, not message-driven
- Envelope codes (07_API_Conventions.md §3) drive copy:
VALIDATION_ERROR→ inline field errors;RESOURCE_NOT_FOUND→ keep input + explain (404 quirk §B2);RATE_LIMITED→ backoff + retry after window; 5xx → generic +requestId. - Marks save failures never discard the row edit — dirty state persists (S1 §3).
B9 — Offline-first grading
- Mobile context: teachers grade in classrooms with flaky connectivity. Edits must survive into an offline draft and replay in order when connectivity returns (15_Flutter §Offline). Idempotent upsert makes replay safe.
B10 — Behavioural metrics (client instrumentation (proposed))
- Save attempts per row (convergence ≤ 2, per 01_Product_Overview.md §5), rejection rate (404 quirk encounters), publish-to-view latency, offline-draft replay failures.