Acceptance Criteria — Client Portal Chat
Status: DRAFT Date: 2026-05-01 Author: Tyler AI review / E alignment Linked Stories: C-W1, C-W2, C-W3, C-W4, C-W8
Context: What a Client Actually Sees
When a client logs in to [slug].human.work, they land on /portal/chat. This is their entire experience of the product. They are talking to "Amy" — their named AI + expert specialist. They never see queue items, risk scores, or expert names.
The client's mental model: "I have a dedicated support specialist named Amy at Human.work. I message her when I need something done."
Current Issues (as of 2026-05-01)
| # | Issue | Severity |
|---|---|---|
| 1 | No org slug shown in the top-left title — hardcoded "H" / "human.work" | High |
| 2 | "Message" button on the Workforce pane does not start a thread — nothing happens | High |
| 3 | Chat interface not reachable without knowing the URL — no clear entry point | High |
| 4 | No Specialist assigned or shown — client doesn't know who Amy is | High |
| 5 | Section label says "Direct Messages" — should be "Your Threads" | Medium |
| 6 | Section label says "Your Experts" — should be "Your Specialists" | Medium |
Acceptance Criteria
AC-PC-01 — Org slug shown in sidebar header
Given a client is logged into their portal,
When the chat UI loads,
Then the top-left sidebar header shows the org's slug (e.g. acmecorp) as the workspace name, not a hardcoded string.
Implementation: fetch slug from JWT payload →
orgMemberships[0]→orgSlug, or from a/portal/contextendpoint. Fall back to "human.work" if unavailable.
Acceptance check: Log in as any seeded client user. Top-left shows their org's slug, not "H / human.work".
AC-PC-02 — Specialist name and avatar shown in sidebar and chat header
Given an AM has assigned a Specialist to the client's org, When the client opens the chat, Then the Specialist's first name (e.g. "Amy") and avatar (initials fallback) appear:
- In the sidebar thread list (as the "contact" name)
- In the chat header when a thread is selected
- In the Workforce pane card
Given no Specialist is assigned, Then the name falls back to "Amy" (the generic persona) and the avatar shows "A".
Implementation: call
GET /portal/contextor embed specialist info in the JWT; the API side should return{ specialistName, specialistAvatarUrl, orgSlug, agentName }.
AC-PC-03 — "Message" button on the Workforce pane starts a new thread
Given the client is on the chat page with no thread selected (empty state / Workforce pane visible), When the client clicks the "Message" button on the Specialist card, Then:
- A new conversation is created via
POST /conversations - The sidebar updates to show the new thread
- The chat panel opens on the new thread, ready to type
Acceptance check: Click "Message" on the Specialist card → thread appears in sidebar → user can type and send immediately.
AC-PC-04 — Client can enter chat and send a message
Given the client is in a thread (selected in sidebar or newly created),
When the client types in the message input and presses Enter or the send button,
Then the message is sent via POST /conversations/:id/messages and appears immediately in the message list (optimistic update).
And then within 3 seconds (polling), Amy's reply appears if available.
Acceptance check: Full send→receive loop works end-to-end on dev.
AC-PC-05 — Sidebar section labels use correct terminology
Given the client is viewing the chat sidebar, Then:
- The thread list section is labelled "Your Threads" (not "Direct Messages")
- The specialist section (if present) is labelled "Your Specialists" (not "Your Experts")
AC-PC-06 — Sidebar "New Thread" button creates a conversation
Given the client already has one or more threads, When the client clicks "New Thread" (or "+ New Task"), Then a new conversation is created and selected, matching AC-PC-03 behavior.
AC-PC-07 — Thread list shows correct thread metadata
Given the client has existing threads, When the sidebar renders, Then each thread entry shows:
- Specialist name (or "Amy" fallback)
- A snippet of the last message (truncated at ~40 chars)
- Relative timestamp of last activity
- Active indicator (green dot) if the thread status is
activeoropen
AC-PC-08 — Chat header shows correct specialist and thread info
Given a thread is selected, When the chat panel header renders, Then it shows:
- Specialist name and avatar
- Online/active status ("Online · Support Specialist" or "Conversation resolved")
- Active/Resolved status badge
AC-PC-09 — Empty state leads to first thread naturally
Given the client has no existing threads, When the chat page loads, Then the Workforce pane shows the Specialist card with a "Message" button. When the client clicks "Message", Then a thread is created and the client lands directly in the chat input, ready to type.
There is no dead-end state — the client always has a clear call to action.
AC-PC-10 — Mobile-responsive layout
Given the client accesses the portal on a screen < 640px wide, When no thread is selected, the sidebar is shown full-width. When a thread is selected, the chat panel fills the screen and the sidebar is hidden. A "back" button or swipe/tap gesture returns to the sidebar.
AC-PC-11 — Last-viewed thread is remembered per user (localStorage)
Given the client previously had a thread selected,
When the client returns to /portal/chat,
Then the last-viewed thread is restored automatically (keyed by userId in localStorage).
Key format:
hw_last_thread_{userId}`` →{ conversationId, timestamp }.
AC-PC-12 — Thread title editable inline (optional, P2)
Given a thread is selected,
When the client clicks/double-clicks the thread title in the sidebar or header,
Then an inline edit field appears.
When the client types a new name and presses Enter,
Then the thread title is saved (locally or via PATCH /conversations/:id).
Out of Scope (for this spec)
- Expert-facing queue or escalation UI (separate spec)
- WhatsApp channel integration (deferred — see WHATSAPP.md)
- Client approval flow for high-stakes actions (UI scaffolded as
ApprovalBannerbut API not wired — P0 gap tracked in USER_STORIES.md C-W8) - Conversation search (C-W6, P1 — future sprint)
Test Credentials (Dev)
| Role | Password | |
|---|---|---|
| Client user | See README.md seeded credentials | See README |
Login flow: /login → invite OTP → /portal/chat
Implementation Notes
API surface needed
The chat portal needs the following data that is currently not returned to the frontend:
-
GET /portal/context(new or augment existing/portal/trial-info) — returns:{
"orgSlug": "acmecorp",
"orgName": "Acme Corp",
"specialistName": "Amy",
"specialistFirstName": "Amy",
"specialistAvatarUrl": null,
"agentName": "Amy"
} -
POST /conversationsmust work forrole: "customer"— currently implemented, verify on dev. -
POST /conversations/:id/messagesmust return the sent message — currently implemented.
Frontend changes needed
| Component | Change |
|---|---|
PortalChat.tsx / Sidebar | Replace hardcoded "H" / "human.work" with org slug from context |
PortalChat.tsx / Sidebar | Replace "Direct Messages" with "Your Threads" |
PortalChat.tsx / WorkforceEmptyState | "Message" button wired to handleNew() (check if broken) |
PortalChat.tsx / Sidebar | Show specialist name/avatar instead of hardcoded "Amy"/"A" |
PortalChat.tsx | Load portal context on mount, use for all specialist references |
PortalChat.tsx | Implement last-viewed thread via localStorage |
| Mobile | Add responsive breakpoint — hide sidebar on narrow screens when thread selected |
Sign-off Required
- E — product alignment
- Tyler — implementation assigned
- QA — test pass on dev before merge to staging