@sunboard/react 0.9.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 sunboardhq.com
3
+ Copyright (c) 2026 Sunboard
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,59 +1,267 @@
1
1
  # @sunboard/react
2
2
 
3
- React SDK for [Sunboard](https://sunboardhq.com) — agent-native onboarding for SaaS apps.
3
+ Product tours, tooltips and a Help launcher with an assistant, for your React app.
4
4
 
5
- Renders the in-app checklist, product tours, and hotspots that your AI agent authors and you promote to active in Sunboard.
5
+ [![npm](https://img.shields.io/npm/v/@sunboard/react.svg)](https://www.npmjs.com/package/@sunboard/react)
6
+ [![license](https://img.shields.io/npm/l/@sunboard/react.svg)](./LICENSE)
7
+ ![react](https://img.shields.io/badge/react-18%2B-blue.svg)
6
8
 
7
- > **You usually don't install this by hand.** Run `npm i -g sunboard && sunboard init` in your app and the CLI wires the provider, stylesheet, and environment variables for you. The reference below is for people who want to understand or customize that wiring after the fact.
9
+ You need a Sunboard account. In the Sunboard app, **Settings › Install** has these steps for your
10
+ framework with your key already filled in, and a prompt you can hand to your coding agent.
8
11
 
9
- ## Install
12
+ ## Quickstart
13
+
14
+ ```tsx
15
+ import "@sunboard/react/styles.css";
16
+ import { SunboardOverlay, SunboardProvider } from "@sunboard/react";
17
+
18
+ <SunboardProvider connection={{ publishableKey: SUNBOARD_KEY, userId: currentUser.id }}>
19
+ <App />
20
+ <SunboardOverlay />
21
+ </SunboardProvider>
22
+ ```
23
+
24
+ That is the whole integration. The steps below put it in the right place for your framework.
25
+
26
+ ## Getting started
27
+
28
+ ### 1. Install the package
10
29
 
11
30
  ```bash
12
- npm i @sunboard/react
31
+ npm install @sunboard/react
32
+ # or
33
+ pnpm add @sunboard/react
34
+ # or
35
+ yarn add @sunboard/react
13
36
  ```
14
37
 
15
- Peer dependencies: `react` >= 18.3 and `react-dom` >= 18.3 (React 19 supported).
38
+ React 18 or later. Every export is client code.
39
+
40
+ ### 2. Add your publishable key
41
+
42
+ Copy it from **Settings › Install**. It is safe in the browser.
43
+
44
+ ```bash
45
+ # Next.js, in .env.local
46
+ NEXT_PUBLIC_SUNBOARD_KEY=<your publishable key>
47
+
48
+ # Vite, React Router or TanStack, in .env
49
+ VITE_SUNBOARD_KEY=<your publishable key>
50
+ ```
16
51
 
17
- ## Wiring
52
+ ### 3. Create one Sunboard component
18
53
 
19
- Wrap your app once with `SunboardProvider`, drop `<Sunboard />` somewhere in your layout, and import the default stylesheet:
54
+ It connects to Sunboard, shows tours and the Help launcher, and lets a tour change pages through
55
+ your router. This is the Next.js version:
20
56
 
21
57
  ```tsx
22
- import { SunboardProvider, Sunboard } from "@sunboard/react";
58
+ // app/sunboard.tsx
59
+ "use client";
60
+
23
61
  import "@sunboard/react/styles.css";
62
+ import type { ReactNode } from "react";
63
+ import { useRouter } from "next/navigation";
64
+ import { SunboardOverlay, SunboardProvider } from "@sunboard/react";
24
65
 
25
- export default function RootLayout({ children }) {
66
+ export function Sunboard({
67
+ userId,
68
+ firstName,
69
+ children,
70
+ }: {
71
+ userId: string;
72
+ firstName?: string;
73
+ children: ReactNode;
74
+ }) {
75
+ const router = useRouter();
26
76
  return (
27
77
  <SunboardProvider
28
- runtimeKey={process.env.NEXT_PUBLIC_SUNBOARD_KEY!}
29
- user={{ id: currentUser.id, firstName: currentUser.firstName }}
30
- navigate={(href) => router.push(href)}
78
+ connection={{
79
+ publishableKey: process.env.NEXT_PUBLIC_SUNBOARD_KEY!,
80
+ userId,
81
+ user: { firstName },
82
+ }}
83
+ navigate={(path) => router.push(path)}
31
84
  >
32
85
  {children}
33
- <Sunboard />
86
+ <SunboardOverlay />
34
87
  </SunboardProvider>
35
88
  );
36
89
  }
37
90
  ```
38
91
 
39
- `sunboard init` produces this exact wiring for Next.js (App Router and Pages) and Vite. `sunboard doctor` confirms it's still correct after upgrades.
92
+ Other frameworks change the key, `navigate`, and where the stylesheet loads:
93
+
94
+ | Framework | Key | `navigate` |
95
+ | --- | --- | --- |
96
+ | React Router | `import.meta.env.VITE_SUNBOARD_KEY` | `useNavigate()` from `react-router`, then `(path) => navigate(path)` |
97
+ | TanStack Start | `import.meta.env.VITE_SUNBOARD_KEY` | `useNavigate()` from `@tanstack/react-router`, then `(path) => navigate({ href: path })` |
98
+ | Vite with no router | `import.meta.env.VITE_SUNBOARD_KEY` | Leave it out. |
99
+
100
+ Outside Next.js the file has no `"use client"` line. In TanStack Start, load the stylesheet from
101
+ `head()` in `src/routes/__root.tsx` instead of importing it:
102
+
103
+ ```tsx
104
+ import sunboardCss from "@sunboard/react/styles.css?url";
105
+
106
+ // in head(): links: [{ rel: "stylesheet", href: sunboardCss }]
107
+ ```
108
+
109
+ `useRouter` and `useNavigate` work only inside the router, so mount the component where the
110
+ table below says.
111
+
112
+ ### 4. Wrap your signed-in pages
113
+
114
+ Put the component around the pages a signed-in user sees, and pass that user's id. Use an id that
115
+ stays the same for each person. The first name is optional. With it the assistant greets the
116
+ user ("Hey Sam,"), without it it says "Hey there,". The name stays in the browser.
117
+
118
+ ```tsx
119
+ import { Sunboard } from "./sunboard";
120
+
121
+ <Sunboard userId={currentUser.id} firstName={currentUser.firstName}>
122
+ {children}
123
+ </Sunboard>
124
+ ```
40
125
 
41
- ## What gets rendered
126
+ | Framework | Where |
127
+ | --- | --- |
128
+ | Next.js | `app/layout.tsx`, or the layout of your signed-in area, inside `body` |
129
+ | React Router | Around the `Outlet` in `app/root.tsx`, or inside `BrowserRouter` |
130
+ | TanStack Start | Around the page content in `src/routes/__root.tsx` |
131
+ | Vite | Around `App` in `src/main.tsx` |
42
132
 
43
- - **`<Sunboard />`** — the main entry point. Renders the active checklist, any in-progress tours, and any visible hotspots based on the current route and user.
44
- - **`<SunboardChecklist />`**, **`<SunboardHotspots />`**, **`<SunboardTour />`** — individual surfaces, if you want to place them manually.
45
- - **`useSunboard()`** — hook returning runtime state and imperative helpers like `startTour(key)`, useful for custom product UI.
133
+ A Next.js app with route groups, such as `app/(marketing)/layout.tsx` and
134
+ `app/(app)/layout.tsx`, wraps `children` in the signed-in group's layout only.
46
135
 
47
- ## Theming
136
+ ### 5. Check it works
137
+
138
+ Start your app and sign in. **Settings › Install** in the Sunboard app shows **Connected**. Then
139
+ add your app's address under **Settings › Project**, and use **Preview** on any guide to see it
140
+ in your app.
141
+
142
+ ## Good to know
143
+
144
+ - **The same key works locally and in production.** Visits from `localhost`, `127.0.0.1` and
145
+ `[::1]` are left out of Analytics and do not count as active users.
146
+ - **Content Security Policy.** The package talks to `https://api.sunboardhq.com`, so a
147
+ `connect-src` rule has to allow it.
148
+ - **Server rendering.** The package is client code and says so, so a Next.js server component
149
+ can import your `Sunboard` component without a wrapper.
150
+ - **Styling.** Colors, fonts, the launcher and the tour cards are set on the **Appearance** page
151
+ in the Sunboard app and reach your app without a deploy. The stylesheet import above is the
152
+ only CSS you add.
153
+ - **Preview.** The **Preview** button on a guide in the Sunboard app opens your app with
154
+ `?sunboardPreview=<key>` in the address and shows paused guides as well as live ones, in that
155
+ browser only. `?sunboardPreview=off` ends it.
156
+ - **Users.** The active-user limit on your plan counts the ids you pass as `userId`, so give each
157
+ person one id that never changes.
158
+
159
+ ## Reference
160
+
161
+ ### SunboardProvider
162
+
163
+ | Prop | Type | Default | What it does |
164
+ | --- | --- | --- | --- |
165
+ | `connection` | `{ publishableKey: string; userId: string; user?: { firstName?: string } }` | required | Your publishable key and the signed-in user's id. `user.firstName` is for the assistant's greeting and stays in the browser. |
166
+ | `navigate` | `(path: string) => void \| Promise<void>` | full page load | How a tour moves to another page. Pass your router. Without it, a tour loads the next page in full and carries on there. |
167
+ | `labels` | `LabelOverrides` | English | Your own words for any of the SDK's buttons and text, for example `labels={{ launcher: { open: "Support" } }}`. The rest stay English. `enLabels` has every one. |
168
+ | `mode` | `"auto" \| "light" \| "dark"` | `"auto"` | Light or dark. `"auto"` follows a `dark` or `light` class, or a `data-theme`, `data-mode` or `data-color-scheme` attribute, on `<html>` or `<body>`, then the system setting, and tracks changes live. |
169
+ | `debug` | `boolean` | `false` | Logs what the SDK does to the browser console, each line starting with `[sunboard]`. The lines use `console.debug`, so Chrome shows them only with the Verbose level on. |
170
+
171
+ ### SunboardOverlay
172
+
173
+ Shows tours, tooltips, announcements, banners and the Help pill. Mount it once, anywhere inside
174
+ the provider.
175
+
176
+ | Prop | Type | Default | What it does |
177
+ | --- | --- | --- | --- |
178
+ | `launcher` | `boolean` | `true` | Shows the Help pill. Pass `false` when you build your own, see below. |
179
+
180
+ ### Address switches
181
+
182
+ Add these to any address in your app. The SDK reads each one once and removes it from the
183
+ address.
184
+
185
+ | Switch | What it does |
186
+ | --- | --- |
187
+ | `?sunboardDebug=1` | Turns on `debug` for that page load. |
188
+ | `?sunboardLaunch=<guide key>` | Starts that tour once the guides load. The Preview link on a tour in the Sunboard app carries it, so you can copy the key from there. |
189
+
190
+ ## Your own help menu
191
+
192
+ The Help pill is built on public hooks, so you can show your guides and the assistant in your
193
+ own UI instead. Keep `<SunboardOverlay />` for tours, tooltips, announcements and banners, and
194
+ turn the pill off:
195
+
196
+ ```tsx
197
+ <SunboardOverlay launcher={false} />
198
+ ```
199
+
200
+ Call `useLauncher()` once for your help menu and pass what it returns down.
201
+
202
+ | Field | What it is |
203
+ | --- | --- |
204
+ | `onThisPage` | Guides for the page the user is on. |
205
+ | `elsewhere` | Every other guide. |
206
+ | `ready` | `true` once the guides have loaded. |
207
+ | `tourRunning` | `true` while a tour runs. |
208
+ | `opened()` | Call it when your menu opens, so the open counts in Analytics. |
209
+ | `start(guideKey, replyId?)` | Starts a guide. Pass the id of the answer that offered it, and the conversation gets a line after that answer when the tour ends. |
210
+ | `messages` | The conversation with the assistant, oldest first. Each `Message` has an `id` and a `kind`: `question`, `waiting`, `reply`, `error` or `tour`. A `reply` holds an `AskReply` with the answer's text in `answer` and `guideKey` when it offers a guide to start. |
211
+ | `waiting` | `true` while a question is out and its answer has not come back. |
212
+ | `rateLimitedUntil` | While the user has asked too many questions this hour, the time they can ask again, as an ISO string. Otherwise `undefined`. |
213
+ | `handedOff` | `true` while a tour started from the conversation runs. It turns `false` when the tour ends, the moment to show your panel again. |
214
+ | `ask(question)` | Asks a question, as a follow-up when the conversation has one. A blank question is ignored, and so is one while `waiting` or while `rateLimitedUntil` is set. |
215
+ | `retry()` | Asks the newest question again when no answer came back for it. |
216
+ | `startOver()` | Clears the conversation. The next question starts a new one. |
217
+
218
+ `Menu` and `MenuItem` below stand for your own components:
219
+
220
+ ```tsx
221
+ import { useLauncher } from "@sunboard/react";
222
+
223
+ function HelpMenu() {
224
+ const { onThisPage, elsewhere, opened, start } = useLauncher();
225
+ return (
226
+ <Menu onOpen={opened}>
227
+ {onThisPage.map((guide) => (
228
+ <MenuItem key={guide.key} onSelect={() => start(guide.key)}>
229
+ {guide.title}
230
+ </MenuItem>
231
+ ))}
232
+ <MenuSeparator />
233
+ {elsewhere.map((guide) => (
234
+ <MenuItem key={guide.key} onSelect={() => start(guide.key)}>
235
+ {guide.title}
236
+ </MenuItem>
237
+ ))}
238
+ </Menu>
239
+ );
240
+ }
241
+ ```
48
242
 
49
- Override CSS variables defined in `@sunboard/react/styles.css` to match your app's design system. Import the Sunboard stylesheet *before* your own so your overrides win the cascade.
243
+ Two more hooks help here. `useGuide(key)` returns `{ guide, launchTour }` for one guide, so a
244
+ button anywhere in your app can start a tour. `useTheme()` returns `{ theme, mode, cssVars }`,
245
+ the colors in use as CSS variables to spread onto your menu so it matches the tours.
50
246
 
51
- ## Links
247
+ ## Troubleshoot
52
248
 
53
- - CLI + agent skills: [`sunboard`](https://www.npmjs.com/package/sunboard)
54
- - App: <https://app.sunboardhq.com>
55
- - Documentation: <https://sunboardhq.com>
249
+ - **Nothing shows in my app.** Add `?sunboardDebug=1` to the page address and open the browser
250
+ console with the Verbose level on. A wrong key shows as a failed request. No lines at all means
251
+ the provider is not mounted around the page you are on.
252
+ - **Settings › Install does not say Connected.** It turns Connected on the first request from your
253
+ app, so load a page where the provider is mounted, signed in, and reload the Install tab.
254
+ - **A tour reloads the page instead of changing route.** Pass `navigate` with your router. Without
255
+ it, a tour carries on after a full page load.
256
+ - **The tour cards have no styling.** Import `@sunboard/react/styles.css` once, next to the
257
+ provider. In TanStack Start, load it from `head()` as shown above.
258
+ - **Requests to api.sunboardhq.com are blocked.** Your Content Security Policy needs it in
259
+ `connect-src`.
260
+ - **Next.js says the component needs "use client".** Put `"use client"` on the first line of your
261
+ `sunboard.tsx`, as in step 3. The package itself is already client code.
262
+ - **useRouter or useNavigate throws.** Mount the component inside the router, where the table in
263
+ step 4 says.
56
264
 
57
265
  ## License
58
266
 
59
- MIT — see [LICENSE](./LICENSE).
267
+ MIT