INFORMATION_ARCHITECTURE.md — human.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
human.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/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 Plan, usage, 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 | Plan, usage, payment | Manage the subscription | Current plan, usage vs. quota, payment method | Upgrade plan, update card | Settings sidebar | Stripe 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 human.work.
3. Site map — Expert
/workspace
├── /workspace/queue EXPERT HOME — pending work
│ └── /workspace/queue/[id] Ticket detail (split-pane)
├── /workspace/collab Internal team rooms
├── /workspace/tasks Agentic task plan review (multi-step)
├── /workspace/analytics Personal + pool analytics
├─ ─ /workspace/pools Pool membership view
└── /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/collab | Team rooms | Coordinate with other experts | Rooms list, messages, unread counts | Send message, switch room | Top nav | — |
/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 & pool analytics | Track resolution time, queue depth, AI/Expert mix | Charts: volume, resolve time, confidence histogram | Date range, pool filter | Top nav | — |
/workspace/pools | Pool memberships | Understand workload distribution | Pool cards with queue depth | Open pool | 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/pools [SA only] Expert pool admin
├── /ops/analytics [SA only] Platform metrics
├── /ops/health [SA only] Service health dashboard
├── /ops/billing [SA only] All-org subscriptions
├── /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/pools → /ops/pools
/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 |
/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 (live tool-permissions toggles), Audit tab (placeholder). | Add member, change specialist, extend trial; SA: change member role, toggle tools | 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/pools | SA only | Pool admin | Maintain pool composition | Pool list, member count, queue depth | Create pool, add/remove member | Top nav | Pool detail |
/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 subscriptions | Track revenue, manage Stripe | Subscription table, plan tiers, MRR | Create/cancel sub, sync Stripe | Top nav | Stripe |
/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 (e.g.
acme.human.work) with the slug initial avatar in claret. This grounds the user in their client portal. - 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 (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 · Pools · 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). |
| 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 Felix (the AI agent) or David (the human expert). 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.human.work), not the human.work platform identity. The platform identity is implicit in everything else. - 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 (P0) |
| 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) |
| Per-client channel adapter health | /ops/health (per-client breakout) | SA-P4 (P1) |
| Tool permission management UI | /ops/clients/[id] → Tools tab (live; backed by /orgs/:orgId/tool-permissions) | SA-P5 (P0 — done) |
| Audit log viewer | /ops/clients/[id] → Audit tab (placeholder, no GET /orgs/:id/audit yet) | SA-C10 (P1) |
| Impersonate / reset password / change plan | /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 PoolMembership → routing pool → org)
- 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) or attached to an Org via a routing pool (handles all threads for that org under whichever Specialist is assigned to each thread).
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 human.work). - No chat-panel header. The client is talking to the human.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 human.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.
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