09 — User Behaviour (CRM Module)
- 1. Mental model: "a pipeline, not a folder"
- 2. Capture cadence (reception)
- 3. Follow-up discipline
- 4. Status transitions (server-enforced)
- 5. Admission decisions: terminal states
- 6. Error-handling behaviours
- 7. Sorting & scanning expectations
- 8. Confirmation thresholds
- 9. Habit loops
- 10. Anti-patterns to design out
Behavioral expectations and mental models for CRM users; interaction patterns, error handling, and habits. Shared interaction conventions: 00-shared/08_Interaction_&_Motion.md.
1. Mental model: "a pipeline, not a folder"
Counselors think in stages (new → contacted → qualified → converted | closed).
The UI must make the current stage and the next stage obvious on every lead row
(status chip + next follow-up). Status is a statement of progress, not a tag:
changes are deliberate (confirm on convert; reason on close).
2. Capture cadence (reception)
- Fast entry is sacred: 3 required fields, everything else optional
(08 §2); source pre-set by entry context (walk-in desk →
walk_in). - Duplicate-email 409 must be inline and actionable, not a dead end — the
receptionist cannot improvise while a parent waits (
crm.service.ts:42-47). - Habit: after create → immediately offered "Add follow-up" from the detail view (post-create sheet with prefilled date +2 days).
3. Follow-up discipline
- Mental model: every lead has a next action date. Derived client-side from the
earliest uncompleted
followUps[].scheduledAt(lead.schema.ts:28-31) — no dedicated field exists (gap; see 12_API_Mapping.md §7). - Overdue follow-ups surface as error-toned rows in S1; the day's queue = filter by status + client "overdue first" sort (client-side only today).
- Completed follow-ups must be visibly checkable (timeline checkmark) — the distinction scheduled/completed drives trust.
(forward-looking): push reminder on due date (needs device registry).
4. Status transitions (server-enforced)
| From | To | Rule | Source |
|---|---|---|---|
| any | converted | via Convert action only; blocked if already converted or closed | crm.service.ts:120-125 |
| any | converted | grade + academic year + class must be set | crm.service.ts:145-149 |
| any | closed | allowed; closedAt auto-stamp when no reason | crm.service.ts:94-96 |
| closed | anything | not possible (no reopen endpoint) | — |
UI mirrors these rules as disabled buttons + tooltips, but server 400s are mapped to copy (rule drift handling). Users learn: converted and closed are terminal.
5. Admission decisions: terminal states
- Deciding is a commitment: closed statuses (approved/rejected/waitlisted/
converted) are immutable (
admission.service.ts:257-269). Warnings in S9 copy this explicitly; reject says "Application closes permanently." - Decision only from decidable statuses (
admission.schema.ts:20-25) — UI hides the dialog otherwise. - The workflow timeline is the trust artifact: approver + comment per hop
(
admission.schema.ts:52-67) — surfaced read-only on S6.
6. Error-handling behaviours
| Situation | User expectation | Contract |
|---|---|---|
| Duplicate lead email | Clear "already exists" + path forward | 409 (crm.service.ts:42-47) |
| Convert preconditions missing | Know which field to fix | 400 exact message (crm.service.ts:145-149) |
| Convert already-converted/closed | Action disabled with reason | 400 (crm.service.ts:120-125) |
| Edit closed admission | Form locked, banner explains | 400 (admission.service.ts:257-269) |
| Decision from wrong status | Dialog not offered | 400 (admission.service.ts:141-145) |
| Cross-tenant id | "Not found" — never existence leak | 404 (base.repository.ts:20-30) |
| Offline | Banner + cached list + no writes (CRM has no offline write queue; writes require connectivity) | 00-shared/07 §10 |
| Rate limit | Countdown, no auto-retry | 429 (00-shared/07 §4) |
7. Sorting & scanning expectations
- Default order: newest first (leads/campaigns by
createdAt, admissions bysubmittedAt—lead.repository.ts:31,admission.repository.ts:29). - Users scan by status chip + follow-up date; a sort by next-follow-up is the
top
(proposed)enhancement (serversortparam(planned)).
8. Confirmation thresholds
| Action | Confirm? | Rationale |
|---|---|---|
| Create lead/admission/campaign | No | low cost, reversible via edit |
| Update lead fields | No (inline save) | cheap |
| Status → closed | Soft: reason field, no modal | reversible-in-record, but terminal-ish |
| Convert lead/admission | Yes (modal) | creates User + Student records, irreversible (crm.service.ts:118-176) |
| Decision approve/reject/waitlist | Yes (modal) | terminal + immutable (admission.service.ts:136-170) |
| Add follow-up | No | cheap, append-only |
9. Habit loops
- Daily queue: open Leads → status filter → work overdue-first → log follow-up → next contact.
- Weekly funnel check: Admissions → stats header (conversion rate) → status
counts → chase stale applications (reminder worker
(planned)automates). - Campaign review: Campaigns list → compare lead metrics per campaign → adjust
spend (metrics write path
(planned)).
10. Anti-patterns to design out
- Paper-slip duplication (409 catch + fast capture).
- Re-keying identity at conversion (server derives User/Student from lead fields — UI must not re-ask).
- Ghost pipelines: every lead always visible under some status filter; closed leads
are inspectable, never silently deleted (no delete endpoint today; soft-delete
(planned)). - Waiting on pages: all writes show explicit in-flight state; no silent double submits.