COMPONENT_INVENTORY.md β h.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/*.Note (2026-05-25 doc refresh): Added 17 missing components; marked
PoolCardandWhatsAppQrModaldeprecated. shadcn/ui primitives inui/tracked viacomponents.json(shadcn convention) β lowercase filenames, PascalCase exported names. See theui/section for the additive list. Update (2026-06-01): Pools are now fully dead per ADR-007 + CD-22 (seeVIEWS_BY_ROLE.md).PoolCardsection below retained as historical reference but the component should not be used in new code. Newer components added below:NotificationBell(#915),ReconsentBanner(#1068/#1141),DraftEditModal(#979), and a Framer Motion animations conventions row (#859). Last updated: 2026-06-02.
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 |
workspace/ | Expert workspace queue | Yes β expert-only |
runtime/ | Agent runtime surfaces | Yes β operator-only |
ops/kb/ | Ops KB management | Yes β AM / SuperAdmin |
brand/ | Brand assets | No |
Plus root-level: components/Nav.tsx, components/UserFooter.tsx, components/IntegrationStatus.tsx, components/ImpersonationBanner.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: "pending" | "awaiting_client" | "resolved" | "snoozed"(canonicalConversationStatus;"open"is accepted as a legacy alias ofpending). Noarchivedβ collapsed intoresolved(#2002). See conversation-status-lifecycle. - Design notes:
- Color mapping: pending β warning, awaiting_client β info, resolved β neutral, snoozed β 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).
AutoreplyThresholdβ
- File:
components/shared/AutoreplyThreshold.tsx - Used by: AM setup wizard Step 3, SuperAdmin org config
- Props/states:
value: number(0β101 scale),onChange: (v: number) => void. - Design notes: 4-step radio selector mapping human labels to internal threshold integers β Never=101, Sometimes=90, Often=75, Always=50. Labels are human-readable; internal scale is numeric (101 = never auto-send). Inline with the ConfidenceBar threshold marker.
AvatarUploadβ
- File:
components/shared/AvatarUpload.tsx - Used by: Specialist config, AM profile edit
- Props/states:
currentUrl: string | null,initials: string,onUpload: (url: string) => void,uploadType: UploadType. - Design notes: Square avatar with hover overlay (camera icon). Click opens hidden
<input type="file">. CallsuploadAvatar()API. ShowsAvatarfallback (initials) whencurrentUrlis null.
FormFieldβ
- File:
components/shared/FormField.tsx - Used by: All forms in wizard flows and settings pages
- Props/states:
label,required?,hint?,state: FieldState,children, optional trailing icon slot. - Design notes: Wraps
<label>+ child input + optionalInfoTooltip+ validation message.FieldStatedrives border color (idle/valid/invalid/checking). Conditional-wrapper pitfall: wrapper always renders β do not conditionally render<div>vschildrenor controlled inputs lose in-flight keystrokes on state flip (React unmounts on tree position change).
SetupStepHeaderβ
- File:
components/shared/SetupStepHeader.tsx - Used by: AM New Client wizard steps 1β4
- Props/states:
stepNumber: number,totalSteps?: number,title: string,subtitle: string. - Design notes: Eyebrow (step N of M, muted), heading (
--text-primary, font-display), subtitle (muted). Consistent across all 4 AM wizard steps.
StepProgressBarβ
- File:
components/shared/StepProgressBar.tsx - Used by: AM New Client wizard header
- Props/states:
currentStep: number(0-based),totalSteps?: number. - Design notes: Horizontal 4-step indicator (
WIZARD_LABELS/WIZARD_LABELS_5). Active step uses--accentfill; completed =--success; pending =--border. Label text 11px muted.
Tooltipβ
- File:
components/shared/Tooltip.tsx - Used by:
FormField(viaInfoTooltip), any surface needing hover/tap explainers - Props/states:
content: string | ReactNode,position?: "top" | "bottom" | "left" | "right",maxWidth?: number,children. - Design notes: Desktop: hover-triggered. Mobile: tap-to-toggle.
InfoTooltipis a pre-wired?icon variant exported from the same file. Portal-rendered viauseIdanchor to avoid overflow clipping issues.
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.
- Real-time: Socket.io live update for the open thread; built-in reconnect handles transient disconnects; reconnect banner surfaces sustained drops; no polling fallback (removed in #843).
- 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.
WhatsAppPairingPanelβ
- File:
components/client/WhatsAppPairingPanel.tsx - Used by: Client WhatsApp settings (
/client/settings/integrations) - Props/states: none (self-contained via API calls).
- Design notes: Replaces the deprecated
WhatsAppQrModal. Shows list of linked numbers; generates Twilio pairing codes (pollingcreateWhatsAppPairingCode). Copy-to-clipboard, 5s poll for new-number confirmation, unlink with confirmation. Manages local state: pairing codes, linked numbers list.
ThreadListItemβ
- File:
components/client/ThreadListItem.tsx - Used by:
PortalChatsidebar thread list - Props/states:
conversation: Conversation,selected: boolean,onClick: () => void,editingId?,onRename?,onDelete?. - Design notes: Shows thread name (or fallback from message preview), channel dot indicator (emoji/color per channel), relative timestamp. Hover reveals inline rename/delete action icons.
SlackConnectCardβ
- File:
components/client/SlackConnectCard.tsx - Used by: Client integrations page
- Props/states: self-contained (reads org context from auth).
- Design notes: Shows Slack webhook URL in a copyable code block. Form to test the integration (
testIntegrationAPI). Expandable "what is this?" explainer. UsesChannelIconfor Slack brand treatment.
WhatsAppQrModalβ
- File:
components/client/WhatsAppQrModal.tsx - Used by: WhatsApp integration setup (legacy Baileys path)
- Props/states:
open,qrDataUrl,onClose. - Design notes: Centered modal, QR centered, instructions below, "Refresh QR" button.
- β οΈ DEPRECATED β Baileys WhatsApp service removed per ADR-002. WhatsApp is now Twilio-only using pairing codes (see
WhatsAppPairingPanel). File kept for back-compat reference only; pending deletion in a follow-up chore.
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.(REMOVED β Collab gone; the only internal surface is the "+ Internal note" composer that posts into the same conversation. Seedocs/features/collab.md.)- 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 / awaiting_client / resolved / snoozed; noarchivedβ collapsed intoresolved, #2002). See conversation-status-lifecycle. - 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(legacy β routes removed) - Props/states:
pool: { id, name, domain, memberCount, queueDepth, slaPct },onOpen. - Design notes: Card 200px tall, header (name + domain badge), body stats, footer "View β" link.
- β οΈ DEPRECATED (2026-06-01) β pool concept removed (CD-22 β ADR-007). Routes
/workspace/poolsand/ops/poolsno longer exist. Component retained only for git history reference; pending deletion. Do not import.
NotificationBellβ
- File:
components/shared/NotificationBell.tsx(#915) - Pipeline: backed by the AppNotification REST + Socket.io pipeline β see features/notifications.md for the delivery model, event names, and producers.
- Status (2026-06-05): DEPRECATED on client shell; scoped to non-client shells only. Replaced on
/client/*by the dedicated/client/inboxroute (#1866, commitb20b36c0). Still used by Expert (/workspace/*) and Ops (/ops/*) top-nav layouts. - Used by: Expert + Ops authenticated layouts (top-nav). NOT used in
/client/*layouts β those now link to/client/inboxinstead. - Props/states: pulls from
AppNotificationfeed via/v1/notifications; renders unread count badge + dropdown list. - Design notes:
- 20px bell icon; unread count pill in
--danger-subtlebg /--dangertext. - Dropdown 360px wide, lazy-loaded list, "Mark all read" footer action.
- Real-time updates via Socket.io
notification:newevent.
- 20px bell icon; unread count pill in
ReconsentBannerβ
- File:
components/client/ReconsentBanner.tsx(#1068, #1141) - Used by:
/client/*layout when org policy version has bumped and member hasn't re-consented. - Props/states:
policyVersion,onAccept,onView. - Design notes:
- Sticky banner at top of viewport,
--warning-subtlebg. - Two CTAs: "View changes" (link) + "I agree" (primary button). Dismissible only via accept.
- Sticky banner at top of viewport,
DraftEditModalβ
- File:
components/internal/DraftEditModal.tsx(#979) - Used by: Expert workspace draft review surface β opens when the Expert clicks "Edit" on an AI draft before sending.
- Props/states:
draft,onSave,onCancel; tracks diff vs original for audit. - Design notes:
- Modal 720px wide, textarea autoresizes, character count footer.
- Diff preview tab (red/green inline) before final send.
Framer Motion animations (conventions)β
- Introduced: #859 (2026-05 motion pass).
- Where used: Drawer/Sheet enter/exit, ticket-status transitions, NotificationBell dropdown, ReconsentBanner reveal.
- Conventions:
- Standard ease:
[0.4, 0, 0.2, 1](Material standard), duration 180ms (micro) / 240ms (modal). - Reduced-motion: respect
prefers-reduced-motionβ fall back to opacity-only fades, no translate. - No layout-shifting animations on the chat thread (jank risk during streaming).
- Standard ease:
AutoReplyConfigCardβ
- File:
components/internal/AutoReplyConfigCard.tsx - Used by: AM org config, SuperAdmin settings
- Props/states: self-contained (reads/writes org auto-reply config via
getAutoReplyConfig/updateAutoReplyConfigAPIs). - Design notes: Mode selector (
agent_reply,system_reply,none) with radio-style options + descriptions.system_replyreveals a template textarea (max 2000 chars). Inline save with success/error feedback.
ConfidenceIndicatorβ
- File:
components/internal/ConfidenceIndicator.tsx - Used by: Expert ticket review panels, message metadata
- Props/states:
score: number(0β100),className?. - Design notes: Horizontal bar + "XX% confidence" label. Color: β₯80 β
--success, 50β79 β--warning, <50 β--danger. Inline-flex. Companion toConfidenceBar(which is thinner and label-less).
CorrectionCategorySelectβ
- File:
components/internal/CorrectionCategorySelect.tsx - Used by:
ResolveFormcorrection fields - Props/states:
value: string,onChange: (v: string) => void,categories: CategoryOption[],placeholder?. - Design notes: Styled replacement for native
<select>. Keyboard navigable (ββ Enter Esc). Design-system token colours. Popup list usesz-indexabove modal; managed open/close on outside click.
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}
.h.workin muted monospace. - Reserved-words list and personal-domain list enforced client-side; server is the source of truth.
7. workspace/ β expert workspace queue components
Card (workspace)β
- File:
components/workspace/Card.tsx - Used by: Queue list items, ticket panels, expert workspace surfaces
- Props/states:
children,variant?,style?. - Design notes: Standardizes 8px border-radius for cards, 6px for buttons, 4px for badges. Uses
cardTokensfor background/border tokens.
RiskBadgeβ
- File:
components/workspace/RiskBadge.tsx - Used by: Queue rows, ticket detail header
- Props/states:
level: string | null | undefined,showBar?: boolean,style?. - Design notes: Unified risk-badge replacing duplicated inline implementations. 10px/700 uppercase text, 4px radius.
showBar=trueadds a 3px left border. Token source:RiskTokens.ts(getRiskTokens(level)).
RiskTokens (utility)β
- File:
components/workspace/RiskTokens.ts - Used by:
RiskBadge,QueueItem - Design notes: Exports
getRiskTokens(level): { bg, text, bar }andRiskLeveltype. Single color source for risk levels: critical β danger, high/medium β warning, low β teal, null β neutral.
8. ui/ additions β custom-built primitives
File-casing: shadcn-derived primitives use lowercase filenames (
card.tsx,dialog.tsx); custom-built ones use PascalCase. Both export PascalCase names. shadcn/ui primitives tracked viacomponents.json.
ActionsDropdownβ
- File:
components/ui/ActionsDropdown.tsx - Used by: Data table rows, list action menus
- Props/states:
items: ActionItem[](label, onClick, icon?, variant?). - Design notes: Three-dot
EllipsisVerticaltrigger. Keyboard-navigable.variant: "danger"items render in--dangertext.
DataTableβ
- File:
components/ui/DataTable.tsx - Used by: All ops list views (clients, specialists, experts, invoices)
- Props/states:
columns: Column<T>[],data: T[],emptyMessage?,loading?,onRowClick?. - Design notes: Sortable columns (click header to cycle asc/desc/none). Empty state via
EmptyState. Uses--border,--bg-hovertokens.
StatCardβ
- File:
components/ui/StatCard.tsx - Used by: Analytics summary, ops dashboard
- Props/states:
label,value,hint?,loading?,badge?. - Design notes: Surface-bg card, 2px border, 8px radius. Label 11px muted; value font-display 24px; optional badge slot top-right.
UserStatusBadgeβ
- File:
components/ui/UserStatusBadge.tsx - Used by: User list rows, expert profile headers
- Props/states:
status: string. - Design notes:
Badgewrapper β active β success, invited β warning, suspended β danger, else β default.
EmptyState (empty-state.tsx)β
- File:
components/ui/empty-state.tsx - Used by:
DataTable, any zero-data surface - Props/states:
icon?,title,body?,description?,action?,className?. - Design notes: Centered card with optional icon, bold title, muted body, optional CTA slot.
PageHeader (page-header.tsx)β
- File:
components/ui/page-header.tsx - Used by: All ops and workspace page tops
- Props/states:
eyebrow?,title,subtitle?,action?: ReactNode. - Design notes: Two variants β
"page"(text-3xl) and"section"(text-xl).actionright-aligned.
9. Root-level additions
ImpersonationBannerβ
- File:
components/ImpersonationBanner.tsx - Used by: All authenticated layouts (root)
- Props/states: none (self-contained via
/auth/impersonation-infoAPI). - Design notes: Amber banner pinned at top during SuperAdmin impersonation. Shows impersonated name, role, "End Impersonation" CTA.
10. ops/kb/ β ops KB management components
KbAssignmentNavβ
- File:
components/ops/kb/KbAssignmentNav.tsx - Used by:
/ops/kb/[orgId]/[specialistId]layout - Design notes: Sidebar nav for per-(org Γ Specialist) KB management.
KbDocumentListβ
- File:
components/ops/kb/KbDocumentList.tsx - Used by: Ops KB management page
- Props/states:
orgId,specialistId. - Design notes: Paginated list of KB documents. Delete + re-upload actions.
KbUploadDropzoneβ
- File:
components/ops/kb/KbUploadDropzone.tsx - Used by: Ops KB management page
- Props/states:
orgId,specialistId,onUploaded. - Design notes: Drag-and-drop or click-to-upload. Accepts PDF, DOCX, TXT. Progress indicator.
11. brand/ β brand assets
Logoβ
- File:
components/brand/Logo.tsx - Used by: Navigation bars, login, onboarding
- Props/states:
size?,variant?: "full" | "icon". - Design notes: h.work wordmark.
variant="icon"= standalone icon only.--text-primaryfill (theme-aware).
12. Open issues per component (cross-reference IMPLEMENTATION_STATUS.md)
| Component | Issue |
|---|---|
PortalChat | Approval banner is expert-only; client variant deferred to P2 (C-W8 demoted 2026-05-23 β manual approval via chat for MVP). Search input not in sidebar (C-W6). |
TicketDetail | Defer action shipped (#1012 β Snooze β Defer copy + flow). Reassign UI not yet shipped (E-R11; legacy expert-pool reassign predates ADR-007 expert_access; see USER_FLOWS Flow 5). |
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. β οΈ Deprecated β see above. |
WhatsAppQrModal | Deprecated β Baileys path removed. Replaced by WhatsAppPairingPanel. Pending deletion. |
Nav (legacy) | To be deleted once /integrations and /dashboard migrate. |
| Theme toggle | Sun/moon icons live inline in UserFooter; consider extracting <ThemeToggle/> to standardize. |
Owner: Design + Frontend team