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 (Users Module)

Behavioural rules: how users act on these screens, what the system must do in response, and the guardrails that keep admin operations safe. Backend facts cited; behavioural rules are product policy consistent with the API.


1. Discovery & search behaviour

  • Search is server-side and instant-debounced. q matches firstName, lastName, email, displayName case-insensitively (users.service.ts:92-99). Users type partial names — no leading wildcard cost concern for tenant scale; debounce 300 ms (00-shared/03 AppSearchBar).
  • Typing in search while scrolled to page 7 resets to page 1 — standard paginated-search semantics (00-shared/06 §3.2).
  • Result count announced (live region) per 00-shared/09 §7.

2. Create / duplicate handling

  • Duplicate email is the #1 real-world error (import + manual). Product rule: on 409 the UI offers "Search existing users" — since the email already belongs to someone, the fix is often "re-activate that user" instead of creating a new row.
  • Email is stored lowercase (user.schema.ts:28-29) — the client lowercases previews/display so users aren't surprised by case changes.
  • Phone duplicates are blocked per tenant (partial unique index, user.schema.ts:84-90) — same inline treatment as email.

3. Status-change behaviour matrix

From → ToSystem behaviourUI note
any → activePATCH statusre-enables login for inactive/suspended users (login checks only isDeleted + lockout — auth.service.ts:124-141)
inactivevisible in admin listscopy: "can still sign in if credentials exist" (no server block)
suspendedvisible; login allowed (no suspension block in code — OQ-13)copy must not overpromise a login block
invitedno server support ((planned) invite flow)option hidden until invite endpoint exists
  • Never present status changes as "security lockdown" — the backend enforces only isDeleted and account lockout (auth.service.ts:133-135).

4. Soft-delete behaviour

  • Deleted users vanish from all scoped queries (base.repository.ts:20-30) and cannot log in (users.repository.ts:21-25).
  • Audit trail survives (UserDeletedaudit-write, event-queue-map.ts:12) — but no user-side "recently deleted" view exists; re-creating with the same email works after deletion (no tombstones checked on create — only live docs, users.service.ts:50-54). UI copy: "Re-adding the same email creates a new account."
  • Purge after 30 days (tenant-purge.worker.ts:32-43) — the delete dialog states this.

5. GDPR erasure behaviour

  • Erasure is immediate and irreversible at the UI level (anonymized PII, users.service.ts:188-197; hard delete by job :200-209, tenant-purge.worker.ts:49-56).
  • Product rule: erasure requires the user's written request context; the typed-confirm dialog is the consent gate. Erased emails become erased-<id>@anonymized.invalid (users.service.ts:192) — never shown in lists (doc is soft-deleted same step).
  • No undo; no "erase" on active users without the delete step being visible in the same dialog.

6. Bulk import behaviour

  • Partial success is the norm; the result screen is the contract of truth (imported + errorsusers.service.ts:281).
  • Users fix files iteratively: error CSV → spreadsheet → re-upload; each round rejects already-imported emails (:260-263) — the wizard must make clear "remove previously imported rows or accept these errors".
  • Never auto-dedupe on the client — server is authoritative.
  • Rows import with defaults (Unknown names, en, UTCusers.service.ts:265-271) — preview must surface "Unknown" fallbacks before import so admins can fix names.
  • Bulk-imported users get no UserCreated event (:264-272 calls repo.create directly) → no welcome notification. Behavior rule: result screen states "Accounts created; no emails sent" ((planned) invite emails).

7. Self-service behaviour

  • Users edit their own profile via PATCH /users/:id — client must scope to sub (server self-guard (planned), OQ-3).
  • Preference toggles: instant feedback (optimistic) but always submitted as the full merged object (full-replace semantics, users.service.ts:161-163).
  • Theme system default mirrors OS; changing language is a client-side locale switch on save (server stores the preference only).

8. Role & membership behaviour

  • Roles are managed on the membership, not the user (organization-member.schema.ts:21-25; rbac.service.ts:113-127).
  • Removing a member (DELETE /rbac/members/:id, rbac.controller.ts:75-79) does not delete the user or revoke their tokens — role list in JWT goes stale until next login/refresh (JWT roles embedded at issue time — auth.service.ts:460-476). UI copy: "Role changes apply on next sign-in."
  • org_admin self-demotion/self-removal: allow with confirmation (no server guard) — client warns "you may lose access".

9. Concurrency & multi-device

  • Two admins editing the same user: last-write-wins with version bump (base.repository.ts:57-66) — no conflict error surfaced; the client refreshes from PATCH response so stale forms are overwritten.
  • Two admins creating the same email concurrently: both pass the pre-check, one hits the unique index (user.schema.ts:83) → 500 duplicate-key (not a clean 409 — OQ-14). Client treats 5xx with "try again"; server fix (planned).
  • List refresh after any mutation; no optimistic list-row removal except soft-delete (safe, idempotent, 00-shared/06 §3.5).

10. Error-behaviour policy (per 00-shared/06 §5)

CodeBehaviour
400field errors inline; import errors as result rows
401silent refresh → session expiry flow
403hide action; if reached → 403 screen
404"User not found" empty state (never leak existence)
409inline conflict + "search existing" affordance
422(planned) reserved; not produced by users module today
429backoff copy + no auto-retry
5xxgeneric + requestId; retry offered; note concurrency dupes (OQ-14)