Skip to main content

Conversation Status Lifecycle

Authoritative reference for the lifecycle states a conversation (a "thread" in the client portal) can be in, the transitions between them, and which actions move a thread where.

Source of truth: ConversationStatus in api/src/common/entities.ts. The queue mirrors it — QueueItemStatus = ConversationStatus.

States

There are four states. There is no archived state — it was collapsed into resolved in #2002.

StatusMeaning
pendingNeeds Expert action. Default state for new and reopened conversations.
awaiting_clientThe Expert has replied; we are waiting on the client.
resolvedClosed, no further action expected. Terminal state — the per-thread "Resolve" action moves a conversation here.
snoozedTemporarily hidden until a deferral expires; resurfaces automatically with a synthetic activity entry (#393).

"open" is accepted in a few places as a legacy alias for pending (e.g. status-pill rendering); it is not a canonical value and should not be used in new code or docs.

Transitions

pending ◄────► awaiting_client ────► resolved
│ │ │
│ │ └─ Resolve (terminal)
│ └─ Expert replies → awaiting_client
└─ Client sends a new message → pending

snoozed ──[deferral expires]──► pending (auto-resurface, #393)
resolved ──Reopen──► pending (Dismiss also lands at resolved)
  • New + reopened conversations start at pending.
  • Resolve is terminal until someone Reopens it (→ pending).
  • Snooze is a temporary hide that auto-returns to pending when the deferral window expires.

Actions, endpoints, and who can trigger them

ActionEndpointEffectWho
ResolvePATCH /conversations/:id/resolve (alias /archive, deprecated #2002)resolvedAny authenticated user — client, Expert, AM, SuperAdmin (IDOR enforced at the service layer, not by PlatformRolesGuard).
ReopenPATCH /conversations/:id/reopen (alias /unarchive, deprecated)resolvedpendingAny authenticated user (same model as Resolve).
DismissPATCH /conversations/:id/dismissawaiting_clientresolved (client-initiated)Client, from the briefing surface.
Set statusPATCH /conversations/:id/statusany of pending / awaiting_client / resolved / snoozedExpert / AM / SuperAdmin only (PlatformRolesGuard).

resolvedAt is stamped on the first transition into resolved.

UI surfaces

Client portal (/client/chat)

  • Sidebar filter pills: All / Active / Resolved (Active = anything not resolved).
  • Per-thread row actions: Resolve (open thread) and Reopen (resolved thread). These were renamed from "Archive"/"Unarchive" in #2684; there is no separate "Archived" list.
  • The briefing surface groups active threads and a "resolved this week" set, and offers Dismiss on awaiting_client threads.

Expert workspace (/workspace/queue)

  • Status tabs: All / Pending / Awaiting Reply / Resolved / Snoozed (GET /expert-queue?status=all|pending|awaiting_client|resolved|snoozed).
  • Status dropdown on the ticket header writes via PATCH /conversations/:id/status.

Not this field

The organization lifecycle (organizations.status: pending_am_setup, active, deactivated, plus "auto-archived" stale-org handling) is a separate concept. "Archived" wording in docs/archive/ and docs/_archive/ refers to archival snapshots, not conversation status.

See also

  • #2002 — backend collapse of archived into resolved.
  • #2684 — client portal "Archive"→"Resolve"/"Reopen" rename + removal of the client-only archived notion.
  • #393 — snooze auto-resurface.