Skip to main content

Acceptance Criteria β€” Client Portal Chat

Status: PARTIAL (revised 2026-05-25 after dev walkthrough) Original date: 2026-05-01 Author: Tyler AI review / E alignment Linked Stories: C-W1, C-W2, C-W3, C-W4, C-W8 Linked Runbook: docs/testing/MVP_P0_RUNBOOK.md (see Client-portal AC-PC mapping section)


Revision note (2026-05-25)​

This spec was authored 2026-05-01 against an earlier portal UI. A dev walkthrough on 2026-05-25 against dev.h852.work (seed: Acme Financial / Eleanora Ogilvie) found that the portal UI evolved past several of the original ACs. ACs are now classified:

  • ACTIVE β€” still represents the desired behaviour, testable today.
  • OBSOLETE β€” REPLACED β€” the underlying need is met by a different implementation. Keep as historical context; do not test against the old criteria.
  • OPEN BUG β€” desired behaviour confirmed; implementation gap remains.

Status per AC is recorded at the head of each section below. Walkthrough evidence is captured in the runbook AC-PC mapping section.


Context: What a Client Actually Sees​

When a client logs in to [slug].h.work, they land on /client/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 h.work. I message her when I need something done."


Current Issues (as of 2026-05-01)​

#IssueSeverity
1No org slug shown in the top-left title β€” hardcoded "H" / "h.work"High
2"Message" button on the Workforce pane does not start a thread β€” nothing happensHigh
3Chat interface not reachable without knowing the URL β€” no clear entry pointHigh
4No Specialist assigned or shown β€” client doesn't know who Amy isHigh
5Section label says "Direct Messages" β€” should be "Your Threads"Medium
6Section label says "Your Experts" β€” should be "Your Specialists"Medium

Acceptance Criteria​

AC-PC-01 β€” Org slug shown in sidebar header​

Status (2026-05-25): OPEN BUG β†’ fixed in PR #719 (fix(client-portal): derive sidebar avatar initial from orgName). The sidebar avatar rendered a hardcoded H letter regardless of org context. The adjacent text already used {orgName}; only the avatar div was hardcoded. One-line patch in frontend/src/components/client/PortalChat.tsx:288.

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 /client/context endpoint. Fall back to "h.work" if unavailable.

Acceptance check: Log in as any seeded client user. Top-left shows their org's slug, not "H / h.work".


AC-PC-02 β€” Specialist name and avatar shown in sidebar and chat header​

Status (2026-05-26): ACTIVE β€” right-panel pattern. Persona name and avatar surface in the /client/chat dashboard right panel, which now renders the full Specialist profile from GET /client/specialists. Multiple assigned Specialists use a carousel; a single Specialist renders as a plain card with no carousel controls.

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 /client/context or 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​

Status (2026-05-26): OBSOLETE β€” REPLACED. The "Message" button on the Specialist card no longer exists (zero >Message< matches in frontend/src/components/client/). Specialist discovery now happens inside the /client/chat dashboard right panel, and thread creation happens via the sidebar + New thread button (covered by AC-PC-06). Do not re-implement the "Message" button or the standalone /client/specialist discovery page.

Given the client is on the chat page, When the client clicks the sidebar + New thread button, 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 sidebar + New thread β†’ thread appears in sidebar β†’ user can type and send immediately.


AC-PC-04 β€” Client can enter chat and send a message​

Status (2026-05-25): ACTIVE β€” PASS-IMMEDIATE. Send flow verified on dev (message appears in DOM post-send). "Amy's reply within 3s" depends on Expert release per the default autoRespondThreshold=101 config β€” runbook C-W1 already documents the immediate-vs-end-to-end split. No bug here; the AC's 3-second wording should be read as "if the system is configured to auto-reply" β€” which is not the dev default.

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 Amy's reply appears via Socket.io push when available; a reconnect banner is shown on transient disconnect (#843, closes #804 β€” 8s polling fallback removed).

Acceptance check: Full send→receive loop works end-to-end on dev.


AC-PC-05 β€” Sidebar section labels use correct terminology​

Status (2026-05-26): OBSOLETE β€” REPLACED. Zero matches in frontend/src/ for any of the four label strings ("Direct Messages", "Your Threads", "Your Experts", "Your Specialists"). The sidebar redesign collapsed the two-section layout (DMs + Experts list) into a flat thread list under the + New thread button, with Specialist discovery embedded in the /client/chat dashboard right panel and approvals at /client/approvals. The terminology problem the AC was solving (wrong words) no longer exists in this UI.

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​

Status (2026-05-25): ACTIVE β€” PASS. Verified on dev: clicking + New thread increased thread count and rendered a composer textarea. This is now the canonical thread-creation path (replaces the obsolete AC-PC-03).

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​

Status (2026-05-25, reprobe): ACTIVE β€” PASS. Walked 14 rows on dev (aside button[aria-label^='Open thread:']). Each row exposes: avatar initial, thread name, relative timestamp, status badge. Example: E | AC-PC-04 smoke: hello from agent walk | 1h | PENDING. The spec's "green active dot" is replaced by an explicit status-badge text (PENDING, NEEDS YOUR INPUT, etc.) β€” strictly clearer than a colored dot. No code change needed.

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 active or open

AC-PC-08 β€” Chat header shows correct specialist and thread info​

Status (2026-05-25, reprobe): ACTIVE β€” PASS-PARTIAL with spec drift. Chat header located via button[aria-label='Back to briefing'] parent div. Dev renders: thread subject + status badge (e.g. Hi + NEEDS INPUT). The AC's "Online Β· Support Specialist" line was removed in the redesign β€” specialist identity is shown per-message rather than in the header. Not a regression; ambient specialist context lives in the sidebar avatar (AC-PC-01, AC-PC-02). Update the AC wording to match the current pattern, or revisit if Tyler/E want the named-specialist header back.

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​

Status (2026-05-25): ACTIVE β€” PASS-VIA-DIFFERENT-CTA. The dead-end risk is mitigated; the sidebar + New thread button is reachable without any existing thread (verified on dev). The AC's specific "Message" button CTA is obsolete (see AC-PC-03), but the "no dead-end state" PASS criterion is met by the redesigned pattern.

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​

Status (2026-05-25): ACTIVE β€” PASS. Verified on dev at 480px viewport: with a thread selected, sidebar collapses (width=0, height=0). The back-button / swipe-gesture return path was not exercised by the walker and remains untested; tracked in runbook AC-PC mapping for the next UAT pass.

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)​

Status (2026-05-25): OBSOLETE β€” REPLACED. Zero matches in frontend/src/ for hw_last_thread_*, lastThreadId, or last_thread. The portal uses Next.js dynamic routing (/client/chat/[id]) instead β€” the URL IS the state. URL-as-state is strictly better than localStorage: cross-device, shareable, bookmark-able, survives browser data clear. The original AC's goal (return-to-context) is met by URL persistence. Do not re-implement the localStorage key.

Given the client previously had a thread selected, When the client returns to /client/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)​

Status (2026-05-25, reprobe): ACTIVE (P2) β€” PASS. Walked dev: 14 .hw-thread-rename-btn buttons exist (one per row, aria-label="Rename thread: <name>", opacity: 0 until row :hover for touch-friendly hide). Force-click reveals the rename input field (aria-label="Rename thread"); save commits via localStorage[hw_threadNames] + UI update. Implementation matches the AC; no API call observed (renames are local β€” PATCH /conversations/:id is not invoked, which means renames don't persist across browsers/devices). If cross-device persistence is wanted, wire the existing PATCH endpoint into commitRename; otherwise mark this AC as accepted-with-local-only behaviour.

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 ApprovalBanner but API not wired β€” demoted to P2 on 2026-05-23, tracked in USER_STORIES.md C-W8. MVP handles approval manually via the conversation channel.)
  • Conversation search (C-W6, P1 β€” future sprint)

Test Credentials (Dev)​

RoleEmailPassword
Client userSee README.md seeded credentialsSee README

Login flow: /login β†’ invite OTP β†’ /client/chat


Implementation Notes​

API surface needed​

The chat portal needs the following data that is currently not returned to the frontend:

  1. GET /client/context (new or augment existing /client/trial-info) β€” returns:

    {
    "orgSlug": "acmecorp",
    "orgName": "Acme Corp",
    "specialistName": "Amy",
    "specialistFirstName": "Amy",
    "specialistAvatarUrl": null,
    "agentName": "Amy"
    }
  2. POST /conversations must work for role: "customer" β€” currently implemented, verify on dev.

  3. POST /conversations/:id/messages must return the sent message β€” currently implemented.

Frontend changes needed​

ComponentChange
PortalChat.tsx / SidebarReplace hardcoded "H" / "h.work" with org slug from context
PortalChat.tsx / SidebarReplace "Direct Messages" with "Your Threads"
PortalChat.tsx / WorkforceEmptyState"Message" button wired to handleNew() (check if broken)
PortalChat.tsx / SidebarShow specialist name/avatar instead of hardcoded "Amy"/"A"
PortalChat.tsxLoad portal context on mount, use for all specialist references
PortalChat.tsxImplement last-viewed thread via localStorage
MobileAdd 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