COMPONENT_INVENTORY.md — human.work
Every UI component with location, role usage, props/states, and design notes. Cross-reference: VIEWS_BY_ROLE.md, FRONTEND_ARCHITECTURE.md, DESIGN_PRINCIPLES.md. Last updated: 2026-05-04.
Note (2026-05-03): Expert pool UI (
PoolCard,/ops/pools,/workspace/pools) is disabled pending redesign on the new direct Expert ↔ Org model (pool tables removed).PortalChat.tsxnow usesEXPERT_ROLESfrom/client/PortalChat.tsx./portal/*prefix is gone — all client routes use/client/*.
Layout: components are organized into 5 directories under frontend/src/components/:
| Directory | Owner | Role-gating allowed |
|---|---|---|
ui/ | Design system | No — pure primitives |
shared/ | Any | No — must be role-agnostic |
client/ | Client portal team | Yes — client-only logic |
internal/ | Expert / AM / Superadmin team | Yes — operator-only logic |
analytics/ | Analytics surfaces (cross-role) | No |
Plus root-level legacy: components/Nav.tsx, components/UserFooter.tsx, components/IntegrationStatus.tsx, components/slug-availability-input.tsx.
1. ui/ — primitives (design system)
These have no business logic and import only from utilities. They are the lowest layer.
Button
- File:
components/ui/Button.tsx - Used by: All roles (Account Manager wizard, login, integrations, etc.)
- Props/states:
variant: "primary" | "secondary" | "ghost" | "danger",size: "sm" | "md" | "lg",loading,disabled,children, native button props. - Design notes:
- Primary uses
--accentbg + white text; hover--accent-hover; disabled 0.5 opacity. - Secondary uses
--bg-surface+--border+--text-primary; hover--bg-hover. - Ghost has no background; hover
--bg-hover. - Danger uses
--dangersemantic. - Heights: sm 28px, md 36px, lg 44px (touch target).
- 8px border-radius.
- Primary uses
Badge
- File:
components/ui/Badge.tsx - Used by: All roles (status pills, role tags, etc.)
- Props/states:
variant: "default" | "success" | "warning" | "danger" | "info" | "neutral",children. - Design notes:
- 10–11px font, 600 weight, uppercase, 0.04em tracking.
- 2px vertical / 6–8px horizontal padding, 4px radius.
- Each variant uses paired
--{variant}-subtlebg +--{variant}`` text. - Do not use raw hex — always tokens.
Avatar
- File:
components/ui/Avatar.tsx - Used by: All roles
- Props/states:
name,src?,size: number(default 32),color?(override). - Design notes:
- Falls back to deterministic initial avatar using a hash of the name → background color from a curated 8-tone palette.
- The Specialist persona always uses
--accent(claret) regardless of name — this is the workspace identity. - Image avatars are object-cover, circle-clipped.
- Online dot is a child element, not part of Avatar (callers add it).
TicketStatusBadge
- File:
components/ui/TicketStatusBadge.tsx - Used by: Client (read-only), Expert (interactive variant lives elsewhere)
- Props/states:
status: "open" | "pending" | "in_review" | "resolved" | "snoozed" | "archived". - Design notes:
- Color mapping: open → success, pending → warning, in_review → info, resolved → neutral, snoozed → muted, archived → muted.
- 11px font, 20px pill radius.
2. shared/ — role-agnostic utilities
ChannelBadge
- File:
components/shared/ChannelBadge.tsx - Used by: All roles wherever a conversation appears (PortalChat header, queue list, ticket detail)
- Props/states:
channel: "web" | "email" | "whatsapp" | "slack" | "telegram" | "wechat" | "teams". - Design notes:
- Uses
ChannelIconSVG + label. - Each channel has a brand-tinted bg + text via tokens (not raw hex). E.g. WhatsApp: success-subtle bg + success-fg text.
- 11px font, 4px radius, 2/8 padding.
- Uses
ChannelIcon
- File:
components/shared/ChannelIcon.tsx - Used by:
ChannelBadge, integration tiles - Props/states:
channel,size. - Design notes: SVG, currentColor. Use semantically (no embedded color).
RelativeTime
- File:
components/shared/RelativeTime.tsx - Used by: Queue rows, message metadata, ticket lists
- Props/states:
timestamp,format?: "short" | "full". - Design notes: tabular-nums for alignment,
--text-mutedcolor, 11–12px.
Skeleton
- File:
components/shared/Skeleton.tsx - Used by: Queue list loading state, ticket detail loading state, dashboard loading
- Props/states:
width,height,variant?: "text" | "rect" | "circle". - Design notes:
--bg-hoverbg, 4–8px radius, optional pulse animation.
Toast
- File:
components/shared/Toast.tsx - Used by: All roles (mounted at root via
Toaster) - Props/states:
toast.success(msg),toast.error(msg|Error),toast.info(msg). Auto-dismiss 4s. - Design notes:
- Bottom-right stacked, max-width 400.
- Each toast: surface bg + 1px border + 4px left bar in semantic color (success/danger/info/warning).
- Slide-up + fade-in animation (
animate-slide-up). - Click dismisses.
ErrorBoundary
- File:
components/shared/ErrorBoundary.tsx - Used by: Wraps each top-level layout
- Props/states:
fallback?: ReactNode. - Design notes: Friendly fallback UI ("Something went wrong — refresh") with claret retry button.
DarkModeProvider
- File:
components/shared/DarkModeProvider.tsx - Used by: Mounted in
app/layout.tsx; consumed byUserFooterand any component callinguseTheme() - Props/states:
theme: "light" | "dark",toggle(). Persisted inlocalStorage.hw_theme. Sets[data-theme]attribute on<html>. - Design notes: System preference (
prefers-color-scheme: dark) honored on first visit when no stored value.
ServiceWorkerRegistrar
- File:
components/shared/ServiceWorkerRegistrar.tsx - Used by: Mounted in root layout
- Props/states: none (side-effect only).
- Design notes: Registers
/sw.js. Hooks for push notification subscription are stubbed (E-P3 gap).
3. client/ — client portal
PortalChat
- File:
components/client/PortalChat.tsx(~1,500 LOC) - Used by:
/client/chat,/client/chat/[id] - Props/states:
initialId?: string. Internal state:conversations,selectedId,messages,selectedExpertId,sidebarOpen,threadNames. - Design notes:
- Two main subcomponents inside:
Sidebar,ChatPanel(+ExpertProfile,MessageBubble,WorkforceEmptyState,WorkerAvatar). - Sidebar:
--bg-sidebar, 256px wide, three sections (workspace header, Specialists, Threads),UserFooterat bottom. - Bubble shape: outgoing claret
18px 18px 4px 18px, incoming bg-surface with 1px border4px 18px 18px 18px. - Role gating lives here:
EXPERT_ROLES = ["expert", "account_manager", "superadmin"].- Internal notes filtered for non-experts (
if (msg.isInternal && !isExpertViewer) return null). - Status dropdown vs read-only badge:
isExpert ? <ExpertChatOverlay/> : <span/>. - Approval banner / confidence scores / "Expert" sender label / internal note toggle: all
isExpertgated.
- Polling: every 15s for the open thread.
- Active thread row:
--accent-subtlebg + 3px--accentleft border. - Hover:
--bg-hover.
- Two main subcomponents inside:
PortalNav
- File:
components/client/PortalNav.tsx - Used by: All
/client/*non-chat routes (tickets, settings) - Props/states: stateless except
pathnamefromusePathname(). - Design notes:
- Top bar layout, claret avatar logo + workspace name, nav links (Chat / Tickets / Settings), user avatar right.
- Hidden on
/client/chat(full-screen layout).
TrialBanner
- File:
components/client/TrialBanner.tsx - Used by: Top of
/client/*(except chat full-screen) - Props/states: hooks
GET /client/trial-info. Internal:dismissedThisSession. - Design notes:
- Sticky top,
--warning-subtlebg +--warningleft bar, 32px tall. - Copy: "Your trial ends in N days. Add billing to keep service. [Add billing →]"
- Dismissible per session (sessionStorage flag).
- Sticky top,
ChannelConfigForm
- File:
components/client/ChannelConfigForm.tsx - Used by:
/client/settings/channels/new,/client/settings/channels/[id] - Props/states:
channel,initialConfig,onSave. - Design notes: Per-channel form (token, webhook URL, whitelist editor). Test-inbound button. Save / Disconnect.
IntegrationCard
- File:
components/client/IntegrationCard.tsx - Used by:
/client/settings/channels - Props/states:
channel,connected,health: "healthy" | "warning" | "error" | "unknown",onClick. - Design notes: Card 220px tall, channel icon + name + connection status pill + last-event timestamp. Connected uses
--bg-surface, available uses--bg-canvas+ dashed border.
TestResult
- File:
components/client/TestResult.tsx - Used by: Integration detail page (test inbound feature)
- Props/states:
result: { status: "success" | "fail", message, latency, payload }. - Design notes: Inline result panel, semantic color left bar + monospace JSON preview.
WhatsAppQrModal
- File:
components/client/WhatsAppQrModal.tsx - Used by: WhatsApp integration setup
- Props/states:
open,qrDataUrl,onClose. - Design notes: Centered modal, QR centered, instructions below, "Refresh QR" button. Note: today this connects via Twilio number pool primarily; QR flow is for the alternative Baileys WhatsApp service.
4. internal/ — operator surfaces (expert / AM / superadmin)
QueueItem
- File:
components/internal/QueueItem.tsx - Used by:
/workspace/queue,/ops/queue - Props/states:
item: QueueItem,selected,onClick,currentUserId?. - Design notes:
- 64px min-height row.
- Left risk bar 3px (theme-aware bar token: critical → danger, high/medium → warning, low → teal).
- Selected overrides bar with
--accent, bg becomes--accent-subtle. - Customer name (13/500), 2-line message preview (12/secondary), badge row (risk badge with
--risk-*tokens, confidence %, AssigneePill, ChannelBadge). - Hover:
--bg-hover.
TicketDetail
- File:
app/workspace/queue/TicketDetail.tsx(lives at the route, not ininternal/, but functionally an internal component) - Used by:
/workspace/queue/[id],/ops/queueright pane - Props/states:
item: QueueItem,onResolved. - Design notes:
- Header: id, risk badge, channel badge, status dropdown.
- Customer thread + AI suggestion panel with
ConfidenceBar. - Action bar: Accept & Send / Edit / Ignore & Write / Defer / Reassign / Resolve.
- Internal-collab thread rendered inline, dashed amber border.
- Risk badge bg/text uses theme-aware
--risk-*tokens.
ExpertChatOverlay
- File:
components/internal/ExpertChatOverlay.tsx - Used by: Inside
PortalChat.tsx(header) whenisExpert === true - Props/states:
conversation,userId,onStatusChange,onAssign. - Design notes:
- Status dropdown (
<select>styled to match design system). - "Assign to me" button.
- Renders only when the viewer has expert role.
- Status dropdown (
ConfidenceBar
- File:
components/internal/ConfidenceBar.tsx - Used by: AI suggestion panel in
TicketDetail, message metadata for experts - Props/states:
confidence: number(0–100). - Design notes:
- 6px tall horizontal bar.
- Color: 0–49 danger, 50–69 warning, 70–84 info, 85+ success.
- Numeric label right-aligned next to the bar (12/600 tabular-nums).
- Threshold marker (vertical line) at the org's configured threshold.
ResolveForm
- File:
components/internal/ResolveForm.tsx - Used by: Ticket detail "Resolve" action (modal)
- Props/states:
ticketId,onResolved, optional correction fields. - Design notes:
- Modal with: resolution outcome (radio), optional correction notes (textarea), error-type select (E-L2 — currently a gap).
- Primary "Mark resolved", secondary "Cancel".
ConversationHistory
- File:
components/internal/ConversationHistory.tsx - Used by: Expert ticket detail, superadmin org detail
- Props/states:
conversationId,paginate?. - Design notes: Same
MessageBubblerendering asPortalChatbut full-history scrollable view; expert-mode by default (shows confidence, internal notes).
StatusBadge
- File:
components/internal/StatusBadge.tsx - Used by: Queue rows, ticket detail header, superadmin org list
- Props/states:
status(lifecycle status: pending / in_review / resolved / snoozed / archived). - Design notes:
- Pill, 11px font, 4px radius.
- Each status has a paired
--{semantic}-subtlebg +--{semantic}`` text.
LiveBadge
- File:
components/internal/LiveBadge.tsx - Used by: Live-online indicators (e.g. "Online · Support Specialist" in chat header) — though that specific copy is currently inline
- Props/states:
status: "online" | "away" | "offline". - Design notes:
- 8px green dot with
animate-pulse-livefor online (1.5s pulse). - Inline-flex, 6px gap to label.
- 8px green dot with
PoolCard
- File:
components/internal/PoolCard.tsx - Used by:
/workspace/pools,/ops/pools - Props/states:
pool: { id, name, domain, memberCount, queueDepth, slaPct },onOpen. - Design notes: Card 200px tall, header (name + domain badge), body stats, footer "View →" link.
Nav (legacy)
- File:
components/Nav.tsx - Used by:
/integrations,/dashboard(legacy routes pre-role-split) - Status: Will be deprecated as those routes migrate to role-prefixed equivalents.
5. analytics/ — chart components (used in any analytics surface)
SummaryCards
- File:
components/analytics/SummaryCards.tsx - Used by:
/workspace/analytics,/ops/analytics(the client/portal/admin/analyticsroute was retired in the 2026-05-02 refactor) - Props/states:
cards: { label, value, delta?, trend? }[]. - Design notes:
- 4-up grid, each card: label (uppercase 10/700 muted), value (24/700), delta (12 with semantic color).
- Border 1px subtle, 12px radius.
VolumeChart
- File:
components/analytics/VolumeChart.tsx - Used by: All analytics surfaces
- Props/states:
data: { date, value }[],range. - Design notes: Line chart, 240px tall. Stroke
--accent, fill--accent-subtle. X-axis ticks 11/muted.
AiVsExpertChart
- File:
components/analytics/AiVsExpertChart.tsx - Used by: All analytics surfaces (esp. C-V4)
- Props/states:
data: { date, ai, expert }[]. - Design notes: Stacked bar chart. AI =
--info, Expert =--accent. Legend top-right.
ConfidenceHistogram
- File:
components/analytics/ConfidenceHistogram.tsx - Used by:
/workspace/analytics,/ops/analytics - Props/states:
bins: number[]. - Design notes: 10 bins (0-9, 10-19, …, 90-100). Each bar colored by confidence band (same scale as
ConfidenceBar).
ResponseTimeChart
- File:
components/analytics/ResponseTimeChart.tsx - Used by: All analytics surfaces
- Props/states:
data: { date, p50, p95 }[]. - Design notes: Two-line chart. P50
--info, P95--warning. Y-axis seconds/minutes.
StatusBreakdownChart
- File:
components/analytics/StatusBreakdownChart.tsx - Used by: All analytics surfaces
- Props/states:
breakdown: { status, count }[]. - Design notes: Donut chart. Each slice uses status-paired token (open/success, pending/warning, etc.). Center label = total count.
TopOrgsTable
- File:
components/analytics/TopOrgsTable.tsx - Used by:
/ops/analytics - Props/states:
orgs: { id, name, slug, ticketCount, mrr, plan }[]. - Design notes: Standard table, 13px body. Row hover
--bg-hover. Click →/ops/clients/[id].
AnalyticsFilters
- File:
components/analytics/AnalyticsFilters.tsx - Used by: All analytics surfaces
- Props/states:
range,onRangeChange, optionalorg,pool,expertfilters. - Design notes: Right-aligned filter row above the dashboard. Date-range picker presets (7d / 30d / 90d / Custom).
shared.tsx
- File:
components/analytics/shared.tsx - Used by: Internal helpers for the analytics charts
- Design notes: Common axes, tooltip styling, color tokens. Centralized to keep all charts consistent.
6. Root-level
UserFooter
- File:
components/UserFooter.tsx - Used by: PortalChat sidebar, Portal admin sidebar. (The unified
/ops/*operator surface and/workspace/*expert surface use the top-nav avatar instead — see INFORMATION_ARCHITECTURE.md §7.) - Props/states: reads JWT (
hw_token) for name/email/initials/platformRole. UsesuseTheme()for the toggle. - Design notes:
- 12px top padding, 8px gap stack.
- Avatar 32px claret circle with initials.
- Name 12/600 primary, email 11/muted.
- Theme toggle 16px icon (sun in dark, moon in light), hover
--text-secondary+--bg-hover. - Footer links 11/muted, role-aware (Expert sees "My Profile", admin sees "Account Settings"+"Billing", etc.).
- "Sign out" 11/muted button.
IntegrationStatus
- File:
components/IntegrationStatus.tsx - Used by: Integration detail pages (currently legacy)
- Status: Keeping for compatibility; new code should use
IntegrationCard+ a fresh detail layout.
slug-availability-input
- File:
components/slug-availability-input.tsx - Used by:
/ops/clients/new(Step 1 — Company Info) - Props/states:
value,onChange,onValidityChange. Internal: debounced API check. - Design notes:
- Input with live validation: ✓ green when available, ✗ red when taken/invalid, spinner while checking.
- Inline preview to the right: ``{value}
.human.workin muted monospace. - Reserved-words list and personal-domain list enforced client-side; server is the source of truth.
7. Open issues per component (cross-reference IMPLEMENTATION_STATUS.md)
| Component | Issue |
|---|---|
PortalChat | Approval banner is expert-only; needs client variant (C-W8). Search input not in sidebar (C-W6). |
TicketDetail | Defer UI not yet shipped (E-R10). Reassign UI not yet shipped (E-R11). |
ResolveForm | No error-type select (E-L2). |
ServiceWorkerRegistrar | Push notification subscription is stubbed (E-P3). |
LiveBadge | Online/away/offline only — no "in another conversation" state. |
PoolCard | No detail page yet — clicking goes nowhere meaningful for non-superadmin. |
Nav (legacy) | To be deleted once /integrations and /dashboard migrate. |
WhatsAppQrModal | Today's WhatsApp is Twilio number pool — QR is for the alternative Baileys path. Disambiguate copy. |
| Theme toggle | Sun/moon icons live inline in UserFooter; consider extracting <ThemeToggle/> to standardize. |
Owner: Design + Frontend team