09 — User Behaviour (Communication Module)
- 1. Consumption behavior
- 2. Composer behavior
- 3. Read-receipt behavior
- 4. Messaging behavior (secondary)
- 5. Failure modes & tolerance
- 6. Trust & privacy
Observed/targeted behavior patterns per persona; informs layout, frequency of actions, and failure modes. Backend constraints cited where they shape behavior.
1. Consumption behavior
| Persona | Cadence | Session type | Core action |
|---|---|---|---|
| Student (Zainab) | 2×/day (morning/evening) | 20-40 s skim | open feed → open unread → auto/explicit read |
| Parent (Mrs. Okoro) | 1×/day + event-triggered | 15 s | deep link → detail → read |
| Teacher (Daniel) | 3-5×/day around classes | 30-60 s | feed + compose class notices |
| Admin (Aisha) | as events occur | 1-2 min | compose, publish, check receipts next day |
Behavioral consequence: the feed must render usable in one hand, show unread at a glance (unread dot 07 §1), and open detail in ≤ 1 tap. Read action should feel free — no confirmation dialogs.
2. Composer behavior
- Authors compose from templates in their head — no template backend
(
(planned),IMPLEMENTATION_PLAN.md:231); provide no template UI today. - Audience choice is the highest-friction step: authors think "my class" or "all
teachers", not "grade: Grade 10, name lookup". The
AudiencePickermust therefore offer the five types in plain words with a recipient-count preview(planned)— backend resolves counts only at publish (announcement.service.ts:68-93). - Draft habit: authors save-then-publish from mobile; publish-from-feed is a
convenience
(planned)(no such route today — publish requires:idafter create).
3. Read-receipt behavior
- Authors check receipts ~24 h after publish; expect a simple read/unread split.
- The unread split is derivable but requires both payloads:
readBy[]fromGET /announcements/:id/readsandtargetUserIdsfrom the list (announcement.service.ts:104-107+:42-43). - Known trap:
AudienceType.ALLhastargetUserIds = [](announcement.service.ts:144-146) — receipts screen must degrade to "list of who read" without counts to avoid misleading "0 of 0 read". - Laggard follow-up (export/remind)
(planned).
4. Messaging behavior (secondary)
- 1:1 staff chat, quick question cadence; unread thread badge drives return visits.
- Realtime expectation — WebSocket
(planned),IMPLEMENTATION_PLAN.md:119; until then poll(forward-looking)is a fallback.
5. Failure modes & tolerance
| Failure | User reaction | Mitigation in design |
|---|---|---|
| Feed empty | assumes nothing was announced | empty-state CTA for authors; no skeleton ghosting |
| Audience silently empty (typo name) | believes everyone saw it | recipient preview (planned) + warning (06 §3.3) |
| Read tap errors | retries once, then ignores | idempotent $addToSet (announcement.repository.ts:20-33) makes retries safe; silent fire-and-forget + toast |
| Offline feed | expects cached last state | offline banner + last-fetch cache (forward-looking) |
| Duplicate announcements (no edit route) | deletes? can't — no delete route | tolerate duplicates in feed; dedupe by _id client-side |
6. Trust & privacy
- Users believe announcements are private to the tenant — true at API level
(
BaseRepositorytenant scope,announcement.repository.ts:12-18). - Users do not expect read receipts to show who read; admins do. Gate receipts UI
by authorship
(planned)— no RBAC exists yet (permissions.constants.tshas nocommunication.*), so the client hides it for non-authors bycreatedBy == me(announcement.schema.tsBaseSchemacreatedBy;announcement.service.ts:36). - Custom-audience picks expose user ids in payloads (
create-announcement.dto.ts:18-26); never render raw ids, always display names (user lookup(forward-looking)).