INFORMATION_ARCHITECTURE.md β h.work
Canonical IA reference. Maps every screen, route, and navigation pattern to the role that uses it. Cross-reference: USER_STORIES.md, GLOSSARY.md, FRONTEND_ARCHITECTURE.md, VIEWS_BY_ROLE.md, USER_FLOWS.md. Last updated: 2026-05-04.
0. What this platform isβ
h.work is Humanity Protocol's internal operations platform for delivering managed AI+Expert services to client companies. The bulk of the application surface is internal: it's used by HP staff (Experts, Account Managers, Superadmins) the way an Intercom or Zendesk-style tool would be used inside any operations company.
There is also a client portal, but it is intentionally minimal: a chat shell so a client (e.g. Amy at Acme Financial) can message her assigned Specialist persona (Bob), plus a small admin section for Team / Channels / Billing. Clients don't run the platform β they receive a service through it. The Specialist persona is operated for them by Humanity Protocol's Expert team backed by AI agents.
This file documents the IA of all four surfaces and the routing rules between them.
1. Roles at a glanceβ
| Role | Who | DB key | URL prefix | Home route | Navigation pattern |
|---|---|---|---|---|---|
Client (org employees: owner, admin, member, billing) | Employees at the client company | org_* client role | /client/* | /client/chat | Persistent left sidebar inside /client/chat; nested /client/settings/* left sidebar (Profile / Team / Channels / Billing) for owner+admin |
| Expert | Humanity Protocol staff who review AI drafts and reply as the Specialist | expert platform role | /workspace/* | /workspace/queue | Top tab bar (Queue / Tasks / Analytics / Settings) |
| Account Manager | Humanity Protocol staff who onboard and manage client orgs | account_manager | /ops/* | /ops/clients | 48px top nav (Clients Β· Inbox Β· Settings) β same shell as Experts |
| Superadmin | Humanity Protocol operations / cross-org access | superadmin | /ops/* | /ops/clients | 48px top nav (Clients Β· Inbox Β· Queue Β· Pools Β· Analytics Β· Health Β· Billing Β· Team Β· Settings) β strict superset of AM; same shell as Experts; Queue tab carries a pending-count badge |
Role redirect after login is governed by getRoleRedirect() in src/contexts/AuthContext.tsx.
2. Site map β Client (/client/*)β
The client portal is intentionally minimal. The primary surface is the chat with the Specialist. The settings section has four sections only β Profile, Team, Channels, Billing β because clients do not configure the Specialist's behavior, manage tickets from their own end-customers, or operate the platform. Service configuration (agent name, risk threshold, slug) is handled by the AM during onboarding.
/client
βββ /client β 302 β /client/chat
βββ /client/chat CLIENT HOME β Specialist chat shell
β βββ /client/chat/[id] Open thread by id
βββ /client/inbox Per-user notification inbox (#1866, 2026-06-05) β replaces NotificationBell drawer for client shell
βββ /client/onboarding First-run welcome (post-invite)
βββ /client/settings [Profile open to all roles; admin sub-pages owner/admin only]
βββ /client/settings/profile Personal settings (name, avatar, phone, title, bio)
βββ /client/settings/team Team members, roles, invites
βββ /client/settings/channels Connected channels (Slack, Email, WhatsApp, Telegram)
β βββ /client/settings/channels/new Connect a channel
β βββ /client/settings/channels/[id] Channel detail / disconnect
βββ /client/settings/billing Invoices (Lago), payment method
# Legacy redirects (kept for inbound links from invite emails / bookmarks)
/client/settings β /client/settings/profile
/client/admin β /client/admin/team
/client/admin/team β /client/settings/team
/client/admin/billing β /client/settings/billing
/client/admin/channels[/...] β /client/settings/channels[/...]
/client/admin/integrations[/...] β /client/settings/channels[/...]
/client/admin/tickets β /client/chat
/client/admin/settings β /client/settings/team
Per-route detail β Clientβ
| Route | Purpose | Primary goal | Key data | Key actions | Entry from | Exit to |
|---|---|---|---|---|---|---|
/client/chat | Conversational shell with the Specialist | Send a request, watch it get done | Threads list (left), open thread, message composer | New Thread, Send, Rename thread, View Specialist profile | login, all /client/settings/* "Back to Chat" link | Thread detail on send |
/client/chat/[id] | Single thread view | Continue a conversation | All messages, status badge, channel badge | Send, scroll history | Sidebar thread click, deep link | Status changes, resolved state |
/client/settings/profile | Personal preferences | Tweak personal config | Profile, theme, notification toggles | Save profile, change password | UserFooter "Settings" link | β |
/client/onboarding | First-run wizard (Phase 2 of onboarding) | Confirm setup, set password, configure channels | OTP, profile fields, slug confirm, channel toggles | Save each step, Complete | Invite email link | /client/chat after Phase-2 completion |
/client/settings/team | Members & invites | Add/remove team members | Members table, invite form, role select (Member / Admin / Billing) | Invite, change role, remove | Settings sidebar | β |
/client/settings/channels | Connected channels | Audit and manage how Bob reaches the team | Slack/Email/WhatsApp/Telegram tiles with health | Connect, disconnect, test | Settings sidebar | Channel detail |
/client/settings/channels/new | Add a channel | Connect Slack/Email/WhatsApp/Telegram | Channel picker, OAuth flow | Connect | Channels list | Channels list |
/client/settings/channels/[id] | Channel detail | Verify config and run tests | Webhook URL, whitelist, test results | Test, edit whitelist, disconnect | Channels list | Channels list |
/client/settings/billing | Invoices & payment | Review per-Specialist invoices | Upcoming invoice (itemized by Specialist), invoice history, payment method | Download invoice PDF, manage payment method | Settings sidebar | Lago-hosted portal |
What the client portal does NOT include (and why):
- No "All Tickets" inbox. Clients converse with the Specialist via threads in chat β there is no helpdesk-style ticket queue. The admin team page is for managing colleagues, not work items.
- No Specialist-behavior settings (agent name, confidence threshold, escalation rules). These are operator-only and configured by the AM during onboarding.
- No analytics dashboard. Tracked as a P2 gap (USER_STORIES.md C-V*) β when delivered, it will be a usage dashboard for the client, not an Expert-handling-rate dashboard.
- No support inbox for the client's own customers. This is not a helpdesk product. Acme Financial has no end-customers using h.work.
3. Site map β Expertβ
/workspace
βββ /workspace/queue EXPERT HOME β pending work
β βββ /workspace/queue/[id] Ticket detail (split-pane)
βββ /workspace/tasks Agentic task plan review (multi-step)
βββ /workspace/analytics Personal analytics
βββ /workspace/settings Personal expert profile
Per-route detail β Expertβ
| Route | Purpose | Primary goal | Key data | Key actions | Entry from | Exit to |
|---|---|---|---|---|---|---|
/workspace/queue | Pending queue triage | Clear the highest-risk item next | Ticket list with risk + confidence + SLA, filters, SLA strip | Filter (status / risk / org / assignee), sort, open ticket, assign-to-me | login | Ticket detail |
/workspace/queue/[id] | HITL review for one ticket | Accept, edit, or write a response and send as Specialist | Customer thread, AI draft + confidence bar, internal notes, audit trail | Accept & Send, Edit, Write from scratch, Defer, Reassign, Resolve, Add internal note | Queue list, deep link, push notif | Back to queue, next ticket |
/workspace/tasks | Multi-step agentic task plans | Approve/reject task steps | Task list, per-step plan + parameters, confidence | Approve step, Reject with feedback, Re-run | Top nav | Task detail |
/workspace/analytics | Personal analytics | Track resolution time, queue depth, AI/Expert mix | Charts: volume, resolve time, confidence histogram | Date range | Top nav | β |
/workspace/settings | Personal expert profile | Set domain tags, availability, notification prefs | Skills, certs, on-call status | Save | UserFooter | β |
4. Site map β Ops (/ops/*)β
AM and Superadmin share the same
/ops/*surface. SA is a strict superset: same routes, more nav tabs, and visibility across all orgs (vs. only assigned orgs for AM, RLS-enforced).
/ops
βββ /ops/clients OPS HOME β clients (AM: assigned; SA: all)
β βββ /ops/clients/new 5-step new client wizard
β βββ /ops/clients/[id] Client detail (members, channels, specialists, plan, activity)
β βββ /ops/clients/[id]/edit Edit client metadata
β βββ /ops/clients/[id]/setup/ Onboarding wizard steps
β βββ step2 Specialist assignment
β βββ step-3 AM profile
β βββ step-4 Send invite (Phase-0 gate)
βββ /ops/inbox Personal inbox (AM + SA)
βββ /ops/queue [SA only] Global expert queue (cross-org)
βββ /ops/analytics [SA only] Platform metrics
βββ /ops/health [SA only] Service health dashboard
βββ /ops/billing [SA only] All-org invoicing (Lago)
βββ /ops/team [SA only] Internal team (AMs, experts, AI operators)
βββ /ops/settings Personal settings (AM + SA)
# Legacy redirects (kept for inbound links from bookmarks / docs)
/am/orgs β /ops/clients
/am/orgs/new β /ops/clients/new
/am/orgs/[id][/...] β /ops/clients/[id][/...]
/am/settings β /ops/settings
/superadmin/orgs β /ops/clients
/superadmin/orgs/new β /ops/clients/new
/superadmin/orgs/[id] β /ops/clients/[id]
/superadmin/queue β /ops/queue
/superadmin/analytics β /ops/analytics
/superadmin/health β /ops/health
/superadmin/billing β /ops/billing
/superadmin/team β /ops/team
/superadmin/settings β /ops/settings
Per-route detail β Opsβ
| Route | Roles | Purpose | Primary goal | Key data | Key actions | Entry from | Exit to |
|---|---|---|---|---|---|---|---|
/ops/clients | AM, SA | Clients (assigned for AM; all for SA) | Pick the client to work on | Client list (name, slug, status, trial end, last activity); SA sees extra columns: primary AM, MRR, last incident | New client, Open, Search; SA: Impersonate, Suspend, Archive | login | Client detail or wizard |
/ops/clients/new | AM, SA | Wizard step 1 | Capture company info | Company info form (name, slug, industry, domains, country, timezone) | Next β step 2 | Client list | Step 2 |
/ops/clients/[id]/setup/step2 | AM, SA | Wizard step 2 | Assign specialist(s) | Specialist pool, primary toggle, threshold | Assign, Next β step 3 | Step 1 | Step 3 |
/ops/clients/[id]/setup/step-3 | AM, SA | Wizard step 3 | Set AM profile (one-time) | AM display name, avatar, signature | Save, Next β step 4 | Step 2 | Step 4 |
/ops/clients/[id]/setup/step-4 | AM, SA | Wizard step 4 | Phase-0 gate + send invite | Phase-0 checklist, invite preview | Send invite | Step 3 | Client detail |
Client-side onboarding wizard (post-invite) runs under
/client/onboarding/*and includes an Agreements step (2.3a) β ToS / Privacy / AUP / DPA accept gates writing to the legal acceptance ledger; see docs/features/onboarding.md and #1046 / #1048 / #1068. |/ops/clients/[id]| AM, SA | Client detail | Manage one client | Tabs: Overview / Conversations / Specialists / Team. SA additionally sees: action bar (Impersonate, Reset password, Change plan β all "coming soon" stubs), inline org-role select per member with "Deactivate" stub,Tools tab(API only β no UI), Audit tab (placeholder). | Add member, change specialist, extend trial; SA: change member role | Client list | Edit / Wizard / member detail | |/ops/clients/[id]/edit| AM, SA | Edit client | Correct metadata | Form: name, contact, country, timezone | Save | Client detail | Client detail | |/ops/inbox| AM, SA | Personal inbox | Triage messages addressed to the operator | Conversation list | Open | Top nav | Conversation detail | |/ops/queue| SA only | Global queue | Spot systemic surges | All pending tickets across orgs, filters | Filter org / risk / status, drill-in | Top nav | Ticket detail (in workspace context) | |/ops/analytics| SA only | Platform analytics | Board-quality numbers | Volume, auto-resolve rate, top request types, active experts | Date range | Top nav | β | |/ops/health| SA only | Health dashboard | Detect incidents | Service status (API, DB, Redis, channel adapters) | Drill into a service | Top nav | β | |/ops/billing| SA only | All-org invoicing | Track revenue, oversee Lago invoices | MRR, upcoming invoices, invoice history per org, assignment/rate history | Drill into org, regenerate draft invoice | Top nav | Lago | |/ops/team| SA only | Internal team | Manage AMs/experts | User list with platform role | Invite, change role, deactivate | Top nav | User detail | |/ops/settings| AM, SA | Personal settings | Profile | Profile, theme, notifications | Save | Top-nav avatar | β |
6. Navigation model per roleβ
6.1 Client β /client/chat (the home shell)β
- Persistent 256px left sidebar with three stacked groups:
- Identity header β slug-as-subdomain identity label (e.g.
acme.h.work) with the slug initial avatar in claret. This grounds the user in their client portal. (Per CD-12, path-based routing (/client/*) is canonical for navigation; the subdomain host is the public-facing identity per ADR-015.) - Your Specialists β list of Specialist personas assigned to the client, each with name + role label ("Primary specialist" / "Specialist"). Click β expert profile preview pane. Active selection: 3px claret left border + accent-tinted bg.
- Your Threads β chronological thread list. Each row: avatar (Specialist initial), thread label (auto-named from first message or renamed), last-message snippet, relative time, unread count pill. Inline rename (βοΈ on hover). Top of group has
+ New Threadbutton.
- Identity header β slug-as-subdomain identity label (e.g.
- Portal footer β
UserFooter(avatar + name/email, theme toggle, settings links, sign out). - Top bar in chat panel β Specialist avatar + name + online indicator, channel badge, status pill (read-only for clients), hamburger to toggle sidebar.
- Composer β bottom-fixed input (auto-grow textarea, attach button, send button). Slack-style.
6.2 Client β /client/settings/* (separate sidebar)β
- 220px left sidebar with four sections: Profile Β· Team Β· Channels Β· Billing. No icons β text labels only.
- Top of sidebar has a "β Chat" back-link to return to the conversational shell.
- Header above the nav shows the client name (e.g. "Acme Financial"), preferring
portalContext.orgNameand falling back to slug. - Active item: 3px claret left border + accent text + accent-subtle bg.
- Footer: single sign-out (shared with the rest of the app β no surface-specific sign-out).
- Removed in the 2026-05-02 refactor: All Tickets (consolidated into Queue), Analytics, Settings, Integrations (renamed to Channels). Old routes redirect to the closest sensible target (see site map above).
6.3 Expert β /workspace/* (top nav)β
- 48px top header with logo, then horizontal tab bar: Queue Β· Tasks Β· Analytics Β· Settings. Active tab: 2px claret bottom-border + accent text.
- Right side: user name + avatar (claret) with click-to-sign-out.
- No left sidebar at the workspace level β each route may use its own left rail (e.g. queue list as a 300px split-pane).
6.4 Ops β /ops/* (top nav, AM + SA)β
AM and Superadmin share the same /ops/* shell. SA is a strict superset β same routes, more nav tabs, broader org visibility (RLS-enforced).
- 48px top header with H logo + role label ("AM" or "Ops" for SA).
- AM tab bar: Clients Β· Inbox Β· Settings.
- SA tab bar: Clients Β· Inbox Β· Queue Β· Analytics Β· Health Β· Billing Β· Team Β· Settings (strict superset).
- Active tab: 2px claret bottom-border + accent text.
- Queue tab (SA only) carries a live pending-count pill (danger-colored) sourced from
getQueue()polled every 10s. - Right side: user name + avatar (claret) β same affordance as the Experts workspace.
- Migrated to the unified
/ops/*surface on 2026-05-03 (previously/am/*and/superadmin/*).UserFooteris not used here; the top-nav avatar at top-right exposes the user instead.
7. Global elements (every screen)β
| Element | Where it appears | Behavior |
|---|---|---|
UserFooter | Inside the chat sidebar for Client. The Experts workspace and the unified Ops surface (AM + SA) use the top-nav avatar at top-right instead (no UserFooter since the 2026-05-03 layout alignment). | Displays avatar + name/email, theme toggle (sun/moon, 16px), role-aware footer links (Settings, Billing, etc.), Sign out |
| Theme toggle | Inside UserFooter | Sun icon when dark, moon when light. Click flips [data-theme] and persists hw_theme in localStorage. System preference honored on first visit. |
| Toast notifications | Mounted at root (Toaster in app/layout.tsx) | Used for all transient feedback (success, error, info). Fires via toast.success() / toast.error(). |
TrialBanner | Top of all /client/* pages (except /client/chat full-screen and /client/settings/*) | Renders only when org is in trial. Shows days remaining, dismissible per-session. |
| Notification badge | Tab title (favicon badge) and on /workspace/queue "Queue" tab pill ("N new") | Driven by Socket.io expert_queue_item_added. Cleared on view. |
| Service worker | Mounted by ServiceWorkerRegistrar | Required for browser push notifications (when implemented; currently registered but no push handler β see USER_STORIES E-P3). |
| AppNotification feed | notifications table + Socket.io notification:new event (#915, shipped 2026-05-25). On client shell: surfaced via the dedicated /client/inbox route (#1866, 2026-06-05; commit b20b36c0). On Expert + AM/SA shells: header bell-icon dropdown. | Persistent in-app notification feed (distinct from transient toasts). Read/unread state per user; auto-marked read on click. The client-side NotificationBell drawer was replaced by /client/inbox in #1866. |
| Error boundary | Wraps each top-level route layout | Surfaces a friendly fallback UI on render errors. |
| Breadcrumbs | Used in /ops/clients/[id]/setup/* (wizard step pills); /client/settings/channels/[id] ("β Back to Channels") | NOT used in flat lists β drill-down depth is shallow enough that breadcrumbs add noise. |
8. Routing & redirect logicβ
After login (/)β
getRoleRedirect(platformRole):
| platformRole | Redirects to |
|---|---|
superadmin | /ops/clients |
expert | /workspace/queue |
account_manager / am | /ops/clients |
owner / admin / member / billing / fallback | /client/chat |
Cross-prefix guardsβ
| URL prefix | Layout guard | Wrong role gets |
|---|---|---|
/client/* | isAuthenticated() | Unauthenticated β /login?redirect=... (set by proxy.ts middleware) |
/client/settings/* (admin sub-pages: team, channels, billing) | requires owner or admin orgRole | Redirected to /client/chat |
/workspace/* | isAuthenticated() + platformRole β EXPERT_ROLES (= expert, account_manager, superadmin) | Unauthenticated β /login; client role β /client/chat. (Closed in 0.3.1 β frontend now enforces, API double-enforces.) |
/ops/* | requires account_manager / am / superadmin; SA-only sub-routes (/ops/queue, /ops/pools, /ops/analytics, /ops/health, /ops/billing, /ops/team) additionally gate on superadmin | Wrong role redirected to /; AM hitting an SA-only sub-route redirected to /ops/clients |
/am/*, /superadmin/* | legacy β redirect to the matching /ops/* route (see Β§4 site map) | β |
/onboarding/* | invite-token-gated | No token β /login?expired=true |
Notable redirectsβ
/clientβ/client/chat(silently)/loginwhile authenticated β role home
9. Information architecture principlesβ
- One conversational home per role. The role's primary verb is reflected at the home route: clients chat (
/client/chat), experts triage (/workspace/queue), AMs and superadmins manage clients (/ops/clients). All other routes branch from this home. - Deep work, shallow nav. Every role can reach any of their top tasks in β€2 clicks. Sidebars surface ~5β7 items max; further depth comes from drill-down, not menu sprawl.
- One identity, never expose the team. The client sees only the Specialist persona (e.g. Bob). They never see the underlying AI agent or the human Expert reviewing the draft. All sender attribution in the client UI says "Bob" or the agent's display name; "AI" / "Human review" badges describe the handling, not the identity.
- Internal-only details stay internal. Confidence scores, risk levels, internal notes, status dropdowns, and assignment controls are gated on
EXPERT_ROLESand never appear in the client UI β even though some components (e.g.PortalChat) are shared across roles. - Client identity β platform branding. The client portal sidebar header shows the client identity (
acme.h.work), not the h.work platform identity. The platform identity is implicit in everything else. (Theacme.h.workhost is the public-facing subdomain per ADR-015; the route inside it remains path-based (/client/*) per CD-12.) - Read-only by default for clients. Anything stateful that affects support workflow (status, assignment) is read-only for clients. They observe; experts act.
10. Outstanding IA gaps (tracked in USER_STORIES.md)β
| Gap | Affected route(s) | Story ID |
|---|---|---|
| Client-facing high-stakes approval banner | /client/chat, /client/chat/[id] | C-W8 (P2 β demoted 2026-05-23; manual approval via chat for MVP) |
| Business context import wizard | /client/onboarding (new step) β note: not /client/settings/*, the old /client/admin/settings page was retired in the 2026-05-02 refactor | C-O6 (P1) |
| Agent memory visibility for clients | TBD β would need a new admin section if pursued | C-T4 (P2) |
| SLA performance dashboard | TBD β would need a new admin section (analytics page was retired) | C-V5 (P1) |
| Invoice download | /client/settings/billing | C-B3 (P2) |
| Push notifications (mobile/browser) | global | E-P3 (P0) |
| Native mobile app (expert) | global | E-P4 (P1) |
| Expert profile + expertise tagging | /workspace/settings | E-O2/3 (P1) |
| Inline ticket discussion (expert β expert) | /workspace/queue/[id] | E-C3 (P2) |
| Correction categorization UI | /workspace/queue/[id] (resolve flow) | E-L2 (P1) |
| Named account lead per client | /ops/clients/[id] | SA-C9 (P1) |
| Expert leverage analytics | /ops/analytics | SA-E4 (P1) β β shipped (#3199) |
| Per-client channel adapter health | /ops/health (per-client breakout) | SA-P4 (P1) |
| Tool permission management UI | /ops/clients/[id] β | SA-P5 (P1 gap β API exists) |
| Audit log viewer | /ops/clients/[id] β Audit tab (placeholder, no GET /orgs/:id/audit yet) | SA-C10 (P1) |
| Impersonate / reset password | /ops/clients/[id] β SA action bar (stubs, no API yet) | SA-C9 (P1) |
11. Completed migration β /portal/* β /client/*β
The client URL prefix migration is complete. All client routes are served from /client/*. Older bookmarks under /portal/* are not preserved at the route layer (no /portal/* directory exists in the app); inbound links from invite emails and old docs were updated as part of the cutover. Sub-route legacy redirects (e.g. /client/admin/team β /client/settings/team) are retained β see Β§2 site map.
Thread β Specialist Data Modelβ
Added 2026-05-02 alongside the introduction of
conversations.specialist_id.
Previous model (pre-FK)β
A Conversation carried only a loose agent_name string column β assigned at creation by AgentsService.pickAgentName(), which sampled from a per-org agents pool with a hardcoded NAME_POOL fallback. Crucially, the assignment did not consult OrgSpecialistAssignment β so a thread for Acme Financial was equally likely to be labeled with the wrong Specialist name as with "Bob" (Acme's actual primary Specialist), and there was no referential link between threads and the specialists table. This was fixed in the 2026-05-02 refactor.
This produced two visible bugs:
- Every client thread showed the same arbitrary persona name in the chat header ("Amy Β· Support Specialist") regardless of which Specialist was assigned to the org.
- There was no way to filter, sort, or route by Specialist on either the client or expert side.
New model (current)β
Three separate concepts, each with explicit cardinality:
Org βββ M:N βββ OrgSpecialistAssignment βββ 1 βββ Specialist
Β· isPrimary boolean
Β· confidenceThreshold int 0β101
Conversation βββ 0..1 βββ Specialist (new: conversations.specialist_id, nullable FK)
Conversation βββ 1 βββ Org (existing: conversations.org_id)
Expert (User) can map to:
Β· 1..N Specialist personas they staff (via Specialist Team membership)
Β· 1..N Orgs directly (via `expert_access` with `scope='org'`, per ADR-007)
- Org β Specialist is many-to-many. An org can have 0, 1, or N Specialists assigned. A Specialist may serve multiple orgs concurrently (subject to
Specialist.currentOrgIdexclusivity if enforced for that persona). Exactly one assignment per org may haveisPrimary = trueβ that's the default Specialist new threads are routed to. - Conversation β Specialist is optional and at-most-one. New web-channel threads are routed to the org's primary Specialist on creation. Channel-inbound threads (Email / WhatsApp) may stay unassigned until routing decides.
- Expert β Specialist or Org is the staffing layer. An Expert can be tied to a specific Specialist (responds as that persona, via
expert_accesswithscope='specialist') or attached to an Org (handles all threads for that org under whichever Specialist is assigned to each thread, viaexpert_accesswithscope='org'). See ADR-007.
The agent_name string column is retained for backwards compatibility but is no longer the source of truth; when specialist_id is set, agent_name should mirror the Specialist's first name (the service writes both on creation).
What clients see vs. what Experts seeβ
Clients (the portal at /client/chat):
- Sidebar header: their workspace name (e.g. "Acme Financial") and a horizontal pill row of all assigned Specialists with the primary marked by a green dot.
- Thread rows: title from
conversation.subject(server-derived from first message) or "New thread", with a small Specialist-initial avatar resolved per-thread fromconversation.specialistId. If a thread has no Specialist, the avatar reads "H" (for h.work). - No chat-panel header. The client is talking to the h.work service generally; the per-thread Specialist context is conveyed by the avatar in the thread row and (when assigned) the composer placeholder ("Message
{Bob}β¦"). Threads without a Specialist read "Message h.workβ¦". - Specialist filter in the sidebar: clicking a Specialist pill scopes the thread list to threads assigned to that Specialist; clicking again (or the "Clear" link) restores the full list.
Experts (the workspace at /workspace/queue):
- Full visibility into
specialist_id,agent_name, the legacy pool, the confidence/risk pipeline, internal notes, status dropdown, and assignment controls. - The same
PortalChatcomponent renders for an Expert if they navigate to/client/chatβ but the Expert-only floating overlay (status, assign, channel) appears top-right; clients never see it.
Schema changeβ
ALTER TABLE conversations ADD COLUMN specialist_id VARCHAR DEFAULT NULL;
CREATE INDEX conversations_specialist_id_idx
ON conversations (specialist_id)
WHERE specialist_id IS NOT NULL;
No FK constraint is enforced at the DB level (cross-module reference; service-layer guard); add one in a follow-up migration if/when the Specialist table layout is locked down for production RLS.
Update (ADR-020, 2026-05-29): Per the isolation classification matrix, customer-PII tables (conversations, messages, expert_queue_items) now carry
specialist_id NOT NULL+ aCHECK (specialist_id IS NOT NULL)constraint in PR-A..E of #1148β#1153. New rows must always be Specialist-scoped; theNULL-tolerant index above is retained for legacy back-compat rows only.
Limitations / TODOsβ
- Channel-inbound threads still get a Specialist via the legacy
agentsService.pickAgentName()path (findOrCreateByChannel). Routing inbound channels to the primary Specialist is the next iteration. - Reassigning a thread to a different Specialist is not yet exposed in the UI. Backend update is a one-line column write; UI would live in the Expert workspace ticket detail.
- Backfill for existing threads. Pre-FK rows have
specialist_id = NULL; they continue to render with the "H" fallback initial. A backfill script that mapsagent_nameto a Specialist by(org_id, first_name)is a P2 cleanup task β not blocking. PortalSpecialist(with email) andPortalContextSpecialist(no email) are intentionally separate types infrontend/src/lib/api.ts. The former is returned byGET /client/specialists; the latter is embedded ingetPortalContext(). Consider unifying once the surfaces converge.
Owner: Design + Platform team