@bernierllc/onboarding-chat-ui
v0.1.0
Published
React onboarding chat UI: split-pane chat with side panels, progress checklist, completion and rating, attachment dropzone, auto-greeting, and onboarding SSE hooks.
Downloads
306
Readme
@bernierllc/onboarding-chat-ui
React UI component library for onboarding chat experiences. Provides a full-featured streaming chat interface backed by Server-Sent Events (SSE), goal progress tracking, plugin-driven side panels, and completion handoffs — all wired to @bernierllc/onboarding-config-core configuration objects.
Installation
npm install @bernierllc/onboarding-chat-uiPeer dependencies:
npm install react react-dom @bernierllc/onboarding-config-core @bernierllc/onboarding-feature-pluginUsage
import { OnboardingChat } from '@bernierllc/onboarding-chat-ui';
import type { OnboardingConfig } from '@bernierllc/onboarding-config-core';
const config: OnboardingConfig = {
id: 'my-onboarding',
name: 'Getting Started',
persona: {
tone: 'friendly-expert',
greeting: "Hi! Let's get you set up.",
askOneAtATime: true,
},
phases: [{ id: 'setup', title: 'Setup', guidance: 'Configure your account.', order: 1 }],
goals: [
{
id: 'add-logo',
description: 'Add your logo',
required: true,
phase: 'setup',
completionCheck: { type: 'field-present', field: 'logoUrl' },
},
],
completionCriteria: { requireAllGoals: true },
handoffs: [{ id: 'dashboard', type: 'route', label: 'Go to Dashboard', order: 1 }],
features: { enabledPlugins: [], pluginConfig: {} },
};
export default function OnboardingPage() {
return (
<OnboardingChat
chatEndpoint="/api/onboarding/chat"
sessionId="sess-abc123"
userId="user-xyz"
config={config}
onComplete={(status) => console.log('Onboarding complete', status)}
/>
);
}Custom Hooks Usage
Use the low-level hooks directly to build a custom UI:
import { useOnboardingChat, useGoalProgress } from '@bernierllc/onboarding-chat-ui';
function MyCustomChat({ config }) {
const { messages, send, isStreaming, setupStatus, isComplete, error } = useOnboardingChat({
chatEndpoint: '/api/chat',
sessionId: 'sess-1',
userId: 'user-1',
});
const { goalItems, requiredMet, requiredTotal } = useGoalProgress({
goals: config.goals,
setupStatus,
});
return (
<div>
{error && <p>Error: {error.message}</p>}
<p>{requiredMet}/{requiredTotal} goals complete</p>
{messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}
{isComplete && <p>All done!</p>}
<button disabled={isStreaming} onClick={() => send('Hello')}>Send</button>
</div>
);
}API
OnboardingChat Component
Root orchestrator component. Renders the three-column layout: chat pane, progress checklist, and side panel.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| chatEndpoint | string | Yes | POST endpoint for chat messages |
| config | OnboardingConfig | Yes | Onboarding configuration object |
| sessionId | string | Yes | Unique session identifier |
| userId | string | Yes | Current user identifier |
| statusEndpoint | string | No | Endpoint for goal status polling |
| ratingEndpoint | string | No | Endpoint for thumbs up/down ratings |
| panelDescriptors | PluginPanelDescriptor[] | No | Plugin panel slot declarations |
| panelComponents | Record<string, React.ComponentType> | No | React components keyed by panel slot |
| onComplete | (status: SetupStatus \| null) => void | No | Callback when onboarding completes |
| fetchFn | typeof fetch | No | Injectable fetch (useful for tests) |
| className | string | No | Additional CSS class |
ChatPane Component
The message thread plus input area.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| messages | OnboardingMessage[] | Yes | Current message list |
| isStreaming | boolean | Yes | Whether an SSE stream is in progress |
| onSend | (text: string, attachments?: Attachment[]) => void | Yes | Send handler |
| className | string | No | Additional CSS class |
ProgressChecklist Component
Renders goal items with check/incomplete badges, driven by SetupStatus from the SSE stream.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| goals | OnboardingGoal[] | Yes | Goals from OnboardingConfig |
| status | SetupStatus \| null | Yes | Latest status from SSE stream |
| showRequired | boolean | No | Filter to required goals only |
| className | string | No | Additional CSS class |
SidePanel Component
Plugin slot container. Renders the component registered for the currently active panel slot.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| activePanel | string \| null | Yes | Currently active slot key |
| panelComponents | Record<string, React.ComponentType> | Yes | Component map by slot |
| fallback | React.ReactNode | No | Rendered when no active panel |
| className | string | No | Additional CSS class |
CompletionCard Component
Shown when the onboarding is complete.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| onboardingName | string | Yes | Display name of the onboarding flow |
| handoffs | HandoffTarget[] | Yes | Available next steps from config |
| onHandoffClick | (target: HandoffTarget) => void | Yes | Called when user clicks a handoff |
| children | React.ReactNode | No | Optional extra content |
| className | string | No | Additional CSS class |
RatingBar Component
Thumbs up / thumbs down rating widget.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| onRate | (rating: 'up' \| 'down') => void | Yes | Called with the selected rating |
| className | string | No | Additional CSS class |
AttachmentDropzone Component
Drag-and-drop file picker. Converts files to base64 data URLs and calls onAttach.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| onAttach | (attachments: Attachment[]) => void | Yes | Called with converted attachments |
| accept | string | No | File types for the hidden input (e.g. "image/*") |
| maxSizeMB | number | No | Max file size (default: 5 MB) |
| className | string | No | Additional CSS class |
useOnboardingChat Hook
Core hook managing SSE streaming, message accumulation, and state transitions.
import { useOnboardingChat } from '@bernierllc/onboarding-chat-ui';
const {
messages, // OnboardingMessage[]
send, // (text: string, attachments?: Attachment[]) => void
isStreaming, // boolean
toolCallEvents, // ToolCallEvent[]
lastToolCall, // ToolCallEvent | null
setupStatus, // SetupStatus | null
isComplete, // boolean
error, // Error | null
} = useOnboardingChat({
chatEndpoint: '/api/chat',
sessionId: 'sess-1',
userId: 'user-1',
fetchFn: customFetch, // optional, defaults to window.fetch
});Options:
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| chatEndpoint | string | Yes | POST endpoint URL |
| sessionId | string | Yes | Session ID sent in request body |
| userId | string | Yes | User ID sent in request body |
| fetchFn | typeof fetch | No | Injectable fetch (testing/SSR) |
useGoalProgress Hook
Derives display-ready goal items from raw goals and live SetupStatus.
import { useGoalProgress } from '@bernierllc/onboarding-chat-ui';
const { goalItems, requiredMet, requiredTotal, isComplete } = useGoalProgress({
goals: config.goals,
setupStatus,
});usePanelRegistry Hook
Determines which plugin panel slot is active based on recent tool call events.
import { usePanelRegistry } from '@bernierllc/onboarding-chat-ui';
const { activePanel } = usePanelRegistry({
panelDescriptors: config.features.panelDescriptors ?? [],
toolCallEvents,
});useAutoGreeting Hook
Fires an initial greeting message after a configurable delay when the message list is empty.
import { useAutoGreeting } from '@bernierllc/onboarding-chat-ui';
useAutoGreeting({
messages,
send,
greeting: config.persona.greeting,
delayMs: 500, // default
});OnboardingChatProvider / useOnboardingChatContext
Share chat state across a component subtree without prop drilling.
import { OnboardingChatProvider, useOnboardingChatContext } from '@bernierllc/onboarding-chat-ui';
function App() {
return (
<OnboardingChatProvider value={chatState}>
<MyPanel />
</OnboardingChatProvider>
);
}
function MyPanel() {
const { messages, isStreaming } = useOnboardingChatContext();
return <div>{isStreaming ? 'Thinking...' : `${messages.length} messages`}</div>;
}OnboardingUIError
All errors thrown by this package are instances of OnboardingUIError with a code field and optional cause chain.
import { OnboardingUIError } from '@bernierllc/onboarding-chat-ui';
if (error instanceof OnboardingUIError) {
console.error(error.code, error.message, error.cause);
}| Code | Cause |
|------|-------|
| CHAT_REQUEST_FAILED | Non-2xx HTTP response from chat endpoint |
| NO_RESPONSE_BODY | Successful HTTP response but body is null |
| STREAM_ERROR | Exception thrown during streaming (network failure, parse error) |
| FILE_READ_ERROR | FileReader error inside AttachmentDropzone |
Integration Documentation
Logger Integration
This package uses @bernierllc/logger for structured error logging. All caught errors are logged at the error level with full context before propagating to the caller. The logger auto-detects the service environment; no configuration is required.
// Internal usage — no setup needed by consumers
import { logger } from '@bernierllc/logger';
logger.error('OnboardingUIError during chat stream', error, { context });NeverHub Integration
This package supports optional NeverHub integration for real-time event tracking and session telemetry. When @bernierllc/neverhub-adapter is available in the environment and NeverHubAdapter.detect() returns true, the OnboardingChat component registers itself and emits lifecycle events. Core functionality works regardless of NeverHub availability (graceful degradation).
// NeverHub auto-detection pattern (internal)
import { NeverHubAdapter } from '@bernierllc/neverhub-adapter';
if (await NeverHubAdapter.detect()) {
const adapter = new NeverHubAdapter();
await adapter.register({ type: 'onboarding-chat-ui', sessionId });
}To enable NeverHub in your app, install and configure the adapter before rendering OnboardingChat. No additional props are required.
SSE Stream Protocol
The backend chat endpoint must respond with Content-Type: text/event-stream. Each event is a newline-delimited JSON frame:
data: {"type":"delta","delta":"Hello "}\n\n
data: {"type":"delta","delta":"world!"}\n\n
data: {"type":"tool_call","toolName":"preview_site","args":{"url":"https://example.com"}}\n\n
data: {"type":"status","status":{...GoalProgress...}}\n\n
data: {"type":"complete"}\n\n| Event type | Payload fields | Effect |
|------------|---------------|--------|
| delta | delta: string | Appends to the current assistant message |
| tool_call | toolName: string, args: unknown | Pushes to toolCallEvents, updates lastToolCall, activates side panel |
| status | status: GoalProgress | Updates setupStatus and goal checklist |
| complete | — | Sets isComplete: true, calls onComplete callback |
Testing Integration
The package provides fetchFn prop/option for full test isolation without mocking globals:
import { useOnboardingChat } from '@bernierllc/onboarding-chat-ui';
import { renderHook, act } from '@testing-library/react';
const mockFetch: typeof fetch = async () => ({
ok: true,
status: 200,
body: { getReader: () => myCustomReader },
} as unknown as Response);
const { result } = renderHook(() =>
useOnboardingChat({ chatEndpoint: '/api/chat', sessionId: 's', userId: 'u', fetchFn: mockFetch })
);Browser Requirements
This package is browser-only. It uses fetch, ReadableStream, TextDecoder, and FileReader. It does not import Node.js built-ins.
License
Copyright (c) 2025 Bernier LLC. Limited-use license — see package header.
