@sunboard/react 0.7.0 → 0.9.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/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from "react";
2
2
  import { RuntimeBootstrapRequest, RuntimeBootstrapResponse, RuntimeExperience, RuntimeProgress, RuntimeTrackRequest, RuntimeTrackResponse, SunboardRuntimeApiError, SunboardRuntimeClient, SunboardRuntimeClientOptions, TooltipDismissVia } from "@sunboard/core/runtime-api";
3
- import * as react_jsx_runtime1 from "react/jsx-runtime";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
  import { NormalizedSunboardSpec, SunboardHotspot, SunboardPlacement, SunboardStep, SunboardTooltip, SunboardTourStep, SurveyQuestionType, TrackEventName } from "@sunboard/core";
5
5
 
6
6
  //#region src/adapters/navigation.d.ts
@@ -52,6 +52,12 @@ type TourResult = {
52
52
  ok: false;
53
53
  reason: "not-ready" | "missing-user" | "missing-tour";
54
54
  };
55
+ type StepResult = {
56
+ ok: true;
57
+ } | {
58
+ ok: false;
59
+ reason: "not-ready" | "missing-user" | "missing-step" | "not-manual" | "network-error";
60
+ };
55
61
  type TooltipResult = {
56
62
  ok: true;
57
63
  } | {
@@ -106,6 +112,12 @@ type SunboardContextValue = {
106
112
  advanceTour: () => Promise<TourResult>;
107
113
  navigateToStep: (step: SunboardStep) => Promise<void>;
108
114
  startChecklistStep: (step: SunboardStep) => Promise<TourResult | TooltipResult | void>;
115
+ /**
116
+ * Complete a checklist step because the user checked it off. Only steps the
117
+ * spec allows (`manual` not set to false) accept it; the completion is
118
+ * recorded with a "manual" source, distinct from event/tour completions.
119
+ */
120
+ completeChecklistStep: (stepKey: string) => Promise<StepResult>;
109
121
  visibleHotspots: Array<{
110
122
  key: string;
111
123
  hotspot: SunboardHotspot;
@@ -171,10 +183,10 @@ declare function SunboardProvider({
171
183
  identities: identitiesProp,
172
184
  onTrack,
173
185
  children
174
- }: SunboardProviderProps): react_jsx_runtime1.JSX.Element;
186
+ }: SunboardProviderProps): react_jsx_runtime0.JSX.Element;
175
187
  //#endregion
176
188
  //#region src/components/SunboardHotspots.d.ts
177
- declare function SunboardHotspots(): react_jsx_runtime1.JSX.Element;
189
+ declare function SunboardHotspots(): react_jsx_runtime0.JSX.Element;
178
190
  //#endregion
179
191
  //#region src/components/SunboardTooltips.d.ts
180
192
  /**
@@ -183,14 +195,14 @@ declare function SunboardHotspots(): react_jsx_runtime1.JSX.Element;
183
195
  * of the session (without recording a dismissal — the user rejected nothing),
184
196
  * so one stale selector can't block the tooltips behind it.
185
197
  */
186
- declare function SunboardTooltips(): react_jsx_runtime1.JSX.Element | null;
198
+ declare function SunboardTooltips(): react_jsx_runtime0.JSX.Element | null;
187
199
  //#endregion
188
200
  //#region src/components/SunboardTour.d.ts
189
- declare function SunboardTour(): react_jsx_runtime1.JSX.Element | null;
201
+ declare function SunboardTour(): react_jsx_runtime0.JSX.Element | null;
190
202
  //#endregion
191
203
  //#region src/components/Sunboard.d.ts
192
- declare function SunboardChecklist(): react_jsx_runtime1.JSX.Element | null;
193
- declare function SunboardSurvey(): react_jsx_runtime1.JSX.Element | null;
204
+ declare function SunboardChecklist(): react_jsx_runtime0.JSX.Element | null;
205
+ declare function SunboardSurvey(): react_jsx_runtime0.JSX.Element | null;
194
206
  declare const Sunboard: typeof SunboardChecklist & {
195
207
  Checklist: typeof SunboardChecklist;
196
208
  Survey: typeof SunboardSurvey;
@@ -209,6 +221,12 @@ type ChecklistViewStep = {
209
221
  description?: string;
210
222
  badge?: string;
211
223
  actionLabel?: string;
224
+ /**
225
+ * True when the user can complete this step by checking it off (the spec
226
+ * default). False (`manual: false` in the spec) means the step completes
227
+ * only through its instrumented rule; render its checkbox non-interactive.
228
+ */
229
+ manuallyCompletable: boolean;
212
230
  };
213
231
  type ChecklistCompletion = {
214
232
  title: string;
@@ -229,6 +247,11 @@ type UseChecklistResult = {
229
247
  isCompleted: (key: string) => boolean;
230
248
  hasAction: (step: ChecklistViewStep) => boolean;
231
249
  startStep: (step: ChecklistViewStep) => void;
250
+ /**
251
+ * Mark the step complete because the user checked it off. A no-op for
252
+ * steps that are not manually completable or already complete.
253
+ */
254
+ completeStep: (step: ChecklistViewStep) => void;
232
255
  completion?: ChecklistCompletion;
233
256
  dismissCompletion: () => void;
234
257
  };
@@ -321,5 +344,5 @@ type UseTooltipsResult = {
321
344
  */
322
345
  declare function useTooltips(): UseTooltipsResult;
323
346
  //#endregion
324
- export { type ChecklistViewStep, type RuntimeBootstrapRequest, type RuntimeBootstrapResponse, type RuntimeExperience, type RuntimeProgress, type RuntimeTrackRequest, type RuntimeTrackResponse, Sunboard, SunboardChecklist, type SunboardContextValue, SunboardHotspots, SunboardProvider, type SunboardProviderProps, SunboardRuntimeApiError, SunboardRuntimeClient, type SunboardRuntimeClientOptions, SunboardSurvey, SunboardTooltips, SunboardTour, type SunboardUser, type SurveyResult, type SurveyViewOption, type SurveyViewQuestion, type SurveyViewStep, type TooltipResult, type TooltipView, type TourResult, type TrackProperties, type TrackResult, type UseChecklistResult, type UseSurveyResult, type UseTooltipsResult, type VisibleSunboardTooltip, useChecklist, useSunboard, useSurvey, useTooltips };
347
+ export { type ChecklistViewStep, type RuntimeBootstrapRequest, type RuntimeBootstrapResponse, type RuntimeExperience, type RuntimeProgress, type RuntimeTrackRequest, type RuntimeTrackResponse, type StepResult, Sunboard, SunboardChecklist, type SunboardContextValue, SunboardHotspots, SunboardProvider, type SunboardProviderProps, SunboardRuntimeApiError, SunboardRuntimeClient, type SunboardRuntimeClientOptions, SunboardSurvey, SunboardTooltips, SunboardTour, type SunboardUser, type SurveyResult, type SurveyViewOption, type SurveyViewQuestion, type SurveyViewStep, type TooltipResult, type TooltipView, type TourResult, type TrackProperties, type TrackResult, type UseChecklistResult, type UseSurveyResult, type UseTooltipsResult, type VisibleSunboardTooltip, useChecklist, useSunboard, useSurvey, useTooltips };
325
348
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapters/navigation.ts","../src/lib/detect-identities.ts","../src/hooks/sunboard-context.ts","../src/components/SunboardProvider.tsx","../src/components/SunboardHotspots.tsx","../src/components/SunboardTooltips.tsx","../src/components/SunboardTour.tsx","../src/components/Sunboard.tsx","../src/hooks/useSunboard.ts","../src/hooks/useChecklist.ts","../src/hooks/useSurvey.ts","../src/hooks/useTooltips.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapters/navigation.ts","../src/lib/detect-identities.ts","../src/hooks/sunboard-context.ts","../src/components/SunboardProvider.tsx","../src/components/SunboardHotspots.tsx","../src/components/SunboardTooltips.tsx","../src/components/SunboardTour.tsx","../src/components/Sunboard.tsx","../src/hooks/useSunboard.ts","../src/hooks/useChecklist.ts","../src/hooks/useSurvey.ts","../src/hooks/useTooltips.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -260,30 +260,36 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, route, n
260
260
  });
261
261
  }, [storageKey]);
262
262
  const track = useCallback(async (eventName, properties) => {
263
- if (resolvedLoading || !resolvedSpec) return {
264
- ok: false,
265
- reason: "not-ready"
266
- };
267
263
  if (!user) return {
268
264
  ok: false,
269
265
  reason: "missing-user"
270
266
  };
271
- const matchingStepKeys = (resolvedSpec.checklist?.steps ?? []).filter((step) => step.completeWhen?.event === eventName).map((step) => step.key);
267
+ const ready = !resolvedLoading && !!resolvedSpec;
268
+ if (!ready && !(hostedMode && client)) return {
269
+ ok: false,
270
+ reason: "not-ready"
271
+ };
272
+ const matchingStepKeys = ready ? (resolvedSpec.checklist?.steps ?? []).filter((step) => step.completeWhen?.event === eventName).map((step) => step.key) : [];
272
273
  if (matchingStepKeys.length > 0) setCompletedStepKeys((current) => mergeKeys(current, matchingStepKeys));
273
274
  let networkOk = true;
274
- if (hostedMode && client && experienceKey) try {
275
- const response = await client.track({
276
- eventName,
277
- experienceKey,
278
- origin: currentOrigin,
279
- properties,
280
- route: currentRoute,
281
- user: toRuntimeUser(user),
282
- versionId: versionId ?? void 0
283
- });
284
- if (response.progress) applyProgress(response.progress, setCompletedStepKeys, setCompletedTourKeys, setDismissedTooltipKeys);
285
- } catch {
286
- networkOk = false;
275
+ if (hostedMode && client) {
276
+ const scopedKey = ready && resolvedSpec.type !== "survey" ? experienceKey : null;
277
+ try {
278
+ const response = await client.track({
279
+ eventName,
280
+ origin: currentOrigin,
281
+ properties,
282
+ route: currentRoute,
283
+ user: toRuntimeUser(user),
284
+ ...scopedKey ? {
285
+ experienceKey: scopedKey,
286
+ versionId: versionId ?? void 0
287
+ } : {}
288
+ });
289
+ if (response.progress) applyProgress(response.progress, setCompletedStepKeys, setCompletedTourKeys, setDismissedTooltipKeys);
290
+ } catch {
291
+ networkOk = false;
292
+ }
287
293
  }
288
294
  try {
289
295
  await onTrack?.(eventName, properties);
@@ -310,6 +316,52 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, route, n
310
316
  user,
311
317
  versionId
312
318
  ]);
319
+ const completeChecklistStep = useCallback(async (stepKey) => {
320
+ if (resolvedLoading || !resolvedSpec) return {
321
+ ok: false,
322
+ reason: "not-ready"
323
+ };
324
+ if (!user) return {
325
+ ok: false,
326
+ reason: "missing-user"
327
+ };
328
+ const step = (resolvedSpec.checklist?.steps ?? []).find((candidate) => candidate.key === stepKey);
329
+ if (!step) return {
330
+ ok: false,
331
+ reason: "missing-step"
332
+ };
333
+ if (step.manual === false) return {
334
+ ok: false,
335
+ reason: "not-manual"
336
+ };
337
+ setCompletedStepKeys((current) => mergeKeys(current, [stepKey]));
338
+ if (hostedMode && client && experienceKey) try {
339
+ applyProgress((await client.completeStep({
340
+ experienceKey,
341
+ origin: currentOrigin,
342
+ route: currentRoute,
343
+ stepKey,
344
+ user: toRuntimeUser(user),
345
+ versionId: versionId ?? void 0
346
+ })).progress, setCompletedStepKeys, setCompletedTourKeys, setDismissedTooltipKeys);
347
+ } catch {
348
+ return {
349
+ ok: false,
350
+ reason: "network-error"
351
+ };
352
+ }
353
+ return { ok: true };
354
+ }, [
355
+ client,
356
+ currentOrigin,
357
+ currentRoute,
358
+ experienceKey,
359
+ hostedMode,
360
+ resolvedLoading,
361
+ resolvedSpec,
362
+ user,
363
+ versionId
364
+ ]);
313
365
  const submitSurvey = useCallback(async (answers) => {
314
366
  if (resolvedLoading || !resolvedSpec) return {
315
367
  ok: false,
@@ -632,6 +684,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, route, n
632
684
  advanceTour,
633
685
  navigateToStep,
634
686
  startChecklistStep,
687
+ completeChecklistStep,
635
688
  visibleHotspots,
636
689
  dismissedTooltipKeys: effectiveDismissedTooltipKeys,
637
690
  visibleTooltips,
@@ -655,6 +708,7 @@ function SunboardProvider({ spec: specProp, user, loading: loadingProp, route, n
655
708
  advanceTour,
656
709
  navigateToStep,
657
710
  startChecklistStep,
711
+ completeChecklistStep,
658
712
  visibleHotspots,
659
713
  effectiveDismissedTooltipKeys,
660
714
  visibleTooltips,
@@ -893,7 +947,7 @@ function interpolateOptional$1(text, user) {
893
947
  return interpolateTokens(text, user) || void 0;
894
948
  }
895
949
  function useChecklist() {
896
- const { spec, user, loading, completedStepKeys, completedCount, totalSteps, startChecklistStep } = useSunboard();
950
+ const { spec, user, loading, completedStepKeys, completedCount, totalSteps, startChecklistStep, completeChecklistStep } = useSunboard();
897
951
  const [dismissedKey, setDismissedKey] = useState(null);
898
952
  const hasChecklist = Boolean(spec && spec.checklist?.steps.length);
899
953
  const allCompleted = totalSteps > 0 && completedCount >= totalSteps;
@@ -932,7 +986,8 @@ function useChecklist() {
932
986
  title: interpolateTokens(step.title, user ?? null),
933
987
  description: step.description ? interpolateTokens(step.description, user ?? null) : void 0,
934
988
  badge: step.badge ? interpolateTokens(step.badge, user ?? null) : void 0,
935
- actionLabel: actionLabelFor(step)
989
+ actionLabel: actionLabelFor(step),
990
+ manuallyCompletable: step.manual !== false
936
991
  }));
937
992
  }, [rawSteps, user]);
938
993
  const stepsByKey = useMemo(() => {
@@ -961,6 +1016,9 @@ function useChecklist() {
961
1016
  const raw = stepsByKey.get(step.key);
962
1017
  if (raw) startChecklistStep(raw);
963
1018
  },
1019
+ completeStep: (step) => {
1020
+ if (step.manuallyCompletable && !completedStepKeys.has(step.key)) completeChecklistStep(step.key);
1021
+ },
964
1022
  completion: showingCompletion ? completion : void 0,
965
1023
  dismissCompletion
966
1024
  };
@@ -1172,11 +1230,19 @@ const ChevronRightIcon = () => {
1172
1230
 
1173
1231
  //#endregion
1174
1232
  //#region src/components/ChecklistStep.tsx
1175
- function ChecklistStep({ completed, onStart, step }) {
1233
+ function ChecklistStep({ completed, onComplete, onStart, step }) {
1176
1234
  const hasAction = step.actionLabel !== void 0;
1177
- const status = /* @__PURE__ */ jsx("span", {
1235
+ const checkable = step.manuallyCompletable && !completed;
1236
+ const status = checkable ? /* @__PURE__ */ jsx("button", {
1237
+ className: "sunboard-step-status",
1238
+ type: "button",
1239
+ onClick: onComplete,
1240
+ "aria-label": `Mark ${step.title} complete`,
1241
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
1242
+ }) : /* @__PURE__ */ jsx("span", {
1178
1243
  className: "sunboard-step-status",
1179
- "aria-label": completed ? "Completed" : "Incomplete",
1244
+ "data-locked": !completed && !step.manuallyCompletable ? "true" : void 0,
1245
+ "aria-label": completed ? "Completed" : step.manuallyCompletable ? "Incomplete" : "Completes automatically",
1180
1246
  children: completed ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
1181
1247
  });
1182
1248
  const content = /* @__PURE__ */ jsxs("div", {
@@ -1198,23 +1264,20 @@ function ChecklistStep({ completed, onStart, step }) {
1198
1264
  return /* @__PURE__ */ jsx("li", {
1199
1265
  className: "sunboard-step",
1200
1266
  "data-completed": completed,
1201
- children: hasAction ? /* @__PURE__ */ jsxs("button", {
1267
+ children: /* @__PURE__ */ jsxs("div", {
1202
1268
  className: "sunboard-step-row",
1203
- type: "button",
1204
- onClick: onStart,
1205
- "aria-label": `${step.actionLabel} ${step.title}`,
1206
- children: [
1207
- status,
1208
- content,
1209
- /* @__PURE__ */ jsx("span", {
1269
+ "data-interactive": hasAction || checkable ? "true" : void 0,
1270
+ children: [status, hasAction ? /* @__PURE__ */ jsxs("button", {
1271
+ className: "sunboard-step-main",
1272
+ type: "button",
1273
+ onClick: onStart,
1274
+ "aria-label": `${step.actionLabel} ${step.title}`,
1275
+ children: [content, /* @__PURE__ */ jsx("span", {
1210
1276
  className: "sunboard-step-arrow",
1211
1277
  "aria-hidden": "true",
1212
1278
  children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
1213
- })
1214
- ]
1215
- }) : /* @__PURE__ */ jsxs("div", {
1216
- className: "sunboard-step-row",
1217
- children: [status, content]
1279
+ })]
1280
+ }) : content]
1218
1281
  })
1219
1282
  });
1220
1283
  }
@@ -1616,7 +1679,7 @@ function SunboardTourScrim({ rect }) {
1616
1679
  //#region src/components/Sunboard.tsx
1617
1680
  function SunboardChecklist() {
1618
1681
  const [open, setOpen] = useState(true);
1619
- const { visible, title, subtitle, steps, completedCount, totalSteps, remainingCount, progress, isCompleted, startStep, completion, dismissCompletion } = useChecklist();
1682
+ const { visible, title, subtitle, steps, completedCount, totalSteps, remainingCount, progress, isCompleted, startStep, completeStep, completion, dismissCompletion } = useChecklist();
1620
1683
  useEffect(() => {
1621
1684
  if (!completion?.dismissAfterMs) return;
1622
1685
  const handle = window.setTimeout(dismissCompletion, completion.dismissAfterMs);
@@ -1722,6 +1785,7 @@ function SunboardChecklist() {
1722
1785
  className: "sunboard-step-list",
1723
1786
  children: steps.map((step) => /* @__PURE__ */ jsx(ChecklistStep, {
1724
1787
  completed: isCompleted(step.key),
1788
+ onComplete: () => completeStep(step),
1725
1789
  onStart: () => startStep(step),
1726
1790
  step
1727
1791
  }, step.key))
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["identities: Record<string, string>","best: PosthogCandidate | undefined","found: PosthogCandidate[]","SunboardRuntimeClient","via: TooltipDismissVia","flat: FlatUser","current: unknown","interpolateOptional","next"],"sources":["../src/adapters/navigation.ts","../src/lib/detect-identities.ts","../src/hooks/sunboard-context.ts","../src/version.ts","../src/components/SunboardProvider.tsx","../src/lib/interpolate-tokens.ts","../src/hooks/useSunboard.ts","../src/hooks/useChecklist.ts","../src/hooks/useSurvey.ts","../src/icons/index.tsx","../src/components/ChecklistStep.tsx","../src/hooks/useInterpolated.ts","../src/hooks/useTargetElement.ts","../src/components/SunboardHotspots.tsx","../src/components/SunboardTooltips.tsx","../src/components/SunboardTour.tsx","../src/components/Sunboard.tsx","../src/hooks/useTooltips.ts"],"sourcesContent":["export type NavigateCallback = (url: string) => void | Promise<void>;\n\nexport async function navigateWithFallback(\n url: string,\n navigate?: NavigateCallback\n): Promise<void> {\n if (navigate) {\n await navigate(url);\n return;\n }\n\n if (typeof window !== \"undefined\") {\n window.location.assign(url);\n }\n}\n","/**\r\n * Best-effort probe of analytics providers on the host page, returning the id\r\n * each one files the current visitor under (e.g. PostHog's distinct_id). These\r\n * ride the bootstrap request so the server can join Sunboard's funnel data to\r\n * the customer's analytics; they are inert until the project connects the\r\n * provider in Sunboard.\r\n *\r\n * Everything here is defensive by construction: a broken or half-loaded\r\n * analytics snippet on the customer's page must never break onboarding, so\r\n * every probe is wrapped and failures return nothing.\r\n */\r\n\r\n/**\r\n * The `identities` prop accepted by SunboardProvider:\r\n * - omitted: providers are auto-detected on each bootstrap\r\n * - object: explicit ids, merged over (and winning against) detected ones\r\n * - `false`: detection disabled entirely, nothing is sent\r\n */\r\nexport type IdentitiesProp = Record<string, string> | false;\r\n\r\nexport function resolveIdentities(\r\n prop: IdentitiesProp | undefined\r\n): Record<string, string> | undefined {\r\n if (prop === false) {\r\n return undefined;\r\n }\r\n\r\n const detected = detectAnalyticsIdentities();\r\n const merged = { ...detected, ...prop };\r\n\r\n return Object.keys(merged).length > 0 ? merged : undefined;\r\n}\r\n\r\nfunction detectAnalyticsIdentities(): Record<string, string> {\r\n const identities: Record<string, string> = {};\r\n\r\n if (typeof window === \"undefined\") {\r\n return identities;\r\n }\r\n\r\n const posthogId = detectPosthogId();\r\n if (posthogId) {\r\n identities.posthog = posthogId;\r\n }\r\n\r\n // Future providers (amplitude, mixpanel) add a probe here and nothing else;\r\n // the wire format and storage are already keyed by provider.\r\n\r\n return identities;\r\n}\r\n\r\nfunction detectPosthogId(): string | undefined {\r\n // A live instance is the most direct read, but only snippet installs assign\r\n // `window.posthog`; the posthog-js npm module (the common React setup) never\r\n // touches the global.\r\n const fromInstance = probe(() => {\r\n const posthog = (window as PosthogWindow).posthog;\r\n return typeof posthog?.get_distinct_id === \"function\"\r\n ? posthog.get_distinct_id()\r\n : undefined;\r\n });\r\n if (fromInstance) {\r\n return fromInstance;\r\n }\r\n\r\n // Fall back to the persistence posthog-js writes on init, keyed\r\n // `ph_<projectApiKey>_posthog` with the distinct_id inside: localStorage\r\n // for the default `localStorage`/`localStorage+cookie` modes, the cookie\r\n // for `cookie` mode. Several entries can coexist on one origin (every\r\n // PostHog project ever run there leaves one behind, localhost especially)\r\n // and each carries a different distinct_id, so pick the entry with the\r\n // most recent session activity: only the currently running project keeps\r\n // bumping its `$sesid` timestamp.\r\n const candidates = [\r\n ...posthogCandidatesFromLocalStorage(),\r\n ...posthogCandidatesFromCookie()\r\n ];\r\n\r\n let best: PosthogCandidate | undefined;\r\n for (const candidate of candidates) {\r\n if (!best || candidate.lastActivity > best.lastActivity) {\r\n best = candidate;\r\n }\r\n }\r\n return best?.distinctId;\r\n}\r\n\r\nconst POSTHOG_PERSISTENCE_KEY = /^ph_.+_posthog$/;\r\n\r\ntype PosthogCandidate = {\r\n distinctId: string;\r\n /** Last session activity (`$sesid[0]`); 0 when the entry has none. */\r\n lastActivity: number;\r\n};\r\n\r\nfunction posthogCandidatesFromLocalStorage(): PosthogCandidate[] {\r\n const found: PosthogCandidate[] = [];\r\n try {\r\n const storage = window.localStorage;\r\n for (let index = 0; index < storage.length; index += 1) {\r\n const key = storage.key(index);\r\n if (!key || !POSTHOG_PERSISTENCE_KEY.test(key)) {\r\n continue;\r\n }\r\n const candidate = parsePosthogPersistence(() => storage.getItem(key));\r\n if (candidate) {\r\n found.push(candidate);\r\n }\r\n }\r\n } catch {\r\n // localStorage can be blocked outright (privacy modes); nothing to read.\r\n }\r\n return found;\r\n}\r\n\r\nfunction posthogCandidatesFromCookie(): PosthogCandidate[] {\r\n const found: PosthogCandidate[] = [];\r\n try {\r\n for (const entry of document.cookie.split(\";\")) {\r\n const separator = entry.indexOf(\"=\");\r\n if (separator === -1) {\r\n continue;\r\n }\r\n const name = entry.slice(0, separator).trim();\r\n if (!POSTHOG_PERSISTENCE_KEY.test(name)) {\r\n continue;\r\n }\r\n const candidate = parsePosthogPersistence(() =>\r\n decodeURIComponent(entry.slice(separator + 1))\r\n );\r\n if (candidate) {\r\n found.push(candidate);\r\n }\r\n }\r\n } catch {\r\n // Cookie access can throw in exotic embeds; nothing to read.\r\n }\r\n return found;\r\n}\r\n\r\n/** One persistence entry -> candidate; malformed entries parse to nothing. */\r\nfunction parsePosthogPersistence(\r\n read: () => string | null\r\n): PosthogCandidate | undefined {\r\n try {\r\n const raw = read();\r\n if (!raw) {\r\n return undefined;\r\n }\r\n const parsed = JSON.parse(raw) as {\r\n distinct_id?: unknown;\r\n $sesid?: unknown;\r\n };\r\n if (\r\n typeof parsed.distinct_id !== \"string\" ||\r\n parsed.distinct_id.length === 0\r\n ) {\r\n return undefined;\r\n }\r\n const sesid = parsed.$sesid;\r\n return {\r\n distinctId: parsed.distinct_id,\r\n lastActivity:\r\n Array.isArray(sesid) && typeof sesid[0] === \"number\" ? sesid[0] : 0\r\n };\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Run a probe, keeping only non-empty string results and swallowing throws. */\r\nfunction probe(read: () => unknown): string | undefined {\r\n try {\r\n const value = read();\r\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\ntype PosthogWindow = Window & {\r\n posthog?: {\r\n get_distinct_id?: () => unknown;\r\n };\r\n};\r\n","import { createContext } from \"react\";\nimport type {\n NormalizedSunboardSpec,\n SunboardHotspot,\n SunboardStep,\n SunboardTooltip,\n SunboardTourStep,\n TrackEventName\n} from \"@sunboard/core\";\nimport type { TooltipDismissVia } from \"@sunboard/core/runtime-api\";\n\nexport type SunboardUser = {\n id: string;\n email?: string | null;\n group?: string | null;\n properties?: Record<string, unknown>;\n};\n\nexport type TrackProperties = Record<string, unknown>;\n\nexport type TrackResult =\n | { ok: true }\n | { ok: false; reason: \"not-ready\" | \"missing-user\" | \"network-error\" };\n\nexport type SurveyResult =\n | { ok: true }\n | {\n ok: false;\n reason: \"not-ready\" | \"missing-user\" | \"not-survey\" | \"network-error\";\n };\n\nexport type TourResult =\n | { ok: true }\n | { ok: false; reason: \"not-ready\" | \"missing-user\" | \"missing-tour\" };\n\nexport type TooltipResult =\n | { ok: true }\n | { ok: false; reason: \"not-ready\" | \"missing-user\" | \"missing-tooltip\" };\n\nexport type VisibleSunboardTooltip = {\n key: string;\n tooltip: SunboardTooltip;\n /**\n * True when the user asked for this tooltip (a checklist step's\n * `openTooltip` action), false when it is showing proactively. A requested\n * tooltip shows even if it was dismissed before — the user explicitly asked\n * to be shown where the action happens.\n */\n requested: boolean;\n};\n\nexport type ActiveSunboardTour = {\n key: string;\n name: string;\n step: SunboardTourStep;\n stepIndex: number;\n totalSteps: number;\n};\n\nexport type SunboardContextValue = {\n spec?: NormalizedSunboardSpec | null;\n user?: SunboardUser | null;\n loading: boolean;\n route?: string;\n completedStepKeys: ReadonlySet<string>;\n completedTourKeys: ReadonlySet<string>;\n completedCount: number;\n totalSteps: number;\n activeTour: ActiveSunboardTour | null;\n track: (\n eventName: TrackEventName,\n properties?: TrackProperties\n ) => Promise<TrackResult>;\n /**\n * Submit survey answers for the active survey experience. On success the\n * runtime re-bootstraps so the freshly collected attributes route the user\n * into the next experience in-session.\n */\n submitSurvey: (answers: Record<string, unknown>) => Promise<SurveyResult>;\n startTour: (tourKey: string) => Promise<TourResult>;\n /**\n * Close the active tour. User-initiated dismissals are reported to the\n * runtime API as a negative signal (\"the user bailed at this step\");\n * pass `silent: true` for programmatic closes (e.g. a missing tour\n * target) that say nothing about the user's intent.\n */\n dismissTour: (options?: { silent?: boolean }) => Promise<TourResult>;\n advanceTour: () => Promise<TourResult>;\n navigateToStep: (step: SunboardStep) => Promise<void>;\n startChecklistStep: (\n step: SunboardStep\n ) => Promise<TourResult | TooltipResult | void>;\n visibleHotspots: Array<{ key: string; hotspot: SunboardHotspot }>;\n dismissedTooltipKeys: ReadonlySet<string>;\n /**\n * Tooltips that qualify for the current route, requested-first. Renderers\n * show ONE at a time: the first entry whose target resolves.\n */\n visibleTooltips: VisibleSunboardTooltip[];\n /**\n * Open a tooltip on demand (the checklist's openTooltip action). Navigates\n * are the caller's job; this just marks the tooltip requested so it renders\n * once its route matches and its target is present, dismissal or not.\n */\n openTooltip: (tooltipKey: string) => Promise<TooltipResult>;\n /**\n * Never show this tooltip again. `via: \"close\"` is the negative signal (the\n * default); `via: \"action\"` records that the user took the tooltip's action;\n * `via: \"target\"` records that the user clicked the tooltip's target element.\n */\n dismissTooltip: (\n tooltipKey: string,\n options?: { via?: TooltipDismissVia }\n ) => Promise<TooltipResult>;\n /** Report that a tooltip actually reached the user's eyes (deduped per session). */\n markTooltipSeen: (tooltipKey: string) => void;\n};\n\nexport const SunboardContext = createContext<SunboardContextValue | null>(null);\n","export const SDK_VERSION = \"0.0.0\";\n","import {\r\n useCallback,\r\n useEffect,\r\n useMemo,\r\n useRef,\r\n useState,\r\n type Dispatch,\r\n type ReactNode,\r\n type SetStateAction\r\n} from \"react\";\r\nimport type {\r\n NormalizedSunboardSpec,\r\n SunboardStep,\r\n TrackEventName\r\n} from \"@sunboard/core\";\r\nimport {\r\n DEFAULT_RUNTIME_API_URL,\r\n SunboardRuntimeClient,\r\n type RuntimeExperience,\r\n type RuntimeProgress,\r\n type RuntimeSurvey,\r\n type TooltipDismissVia\r\n} from \"@sunboard/core/runtime-api\";\r\nimport {\r\n navigateWithFallback,\r\n type NavigateCallback\r\n} from \"../adapters/navigation\";\r\nimport { routeMatches } from \"../adapters/routes\";\r\nimport {\r\n resolveIdentities,\r\n type IdentitiesProp\r\n} from \"../lib/detect-identities\";\r\nimport {\r\n SunboardContext,\r\n type ActiveSunboardTour,\r\n type SunboardContextValue,\r\n type SunboardUser,\r\n type TrackProperties\r\n} from \"../hooks/sunboard-context\";\r\nimport { SDK_VERSION } from \"../version\";\r\n\r\nexport type SunboardProviderProps = {\r\n spec?: NormalizedSunboardSpec | null;\r\n user?: SunboardUser | null;\r\n loading?: boolean;\r\n route?: string;\r\n navigate?: NavigateCallback;\r\n /**\r\n * Override the runtime API host. Dev-only — leave unset in production so the\r\n * SDK targets the hosted Sunboard runtime API. Defaults to\r\n * `DEFAULT_RUNTIME_API_URL`.\r\n */\r\n apiUrl?: string;\r\n publishableKey?: string;\r\n /**\r\n * Analytics-provider ids for joining Sunboard's funnel data to your product\r\n * analytics. Omit it and installed providers (PostHog) are detected\r\n * automatically, on each bootstrap and for a few seconds after, so a\r\n * provider that initializes late is still captured. Pass explicit ids to\r\n * override detection; pass `false` to disable capture entirely. Captured ids\r\n * are inert until the provider is connected to the project in Sunboard.\r\n */\r\n identities?: IdentitiesProp;\r\n onTrack?: (\r\n eventName: TrackEventName,\r\n properties?: TrackProperties\r\n ) => void | Promise<void>;\r\n children: ReactNode;\r\n};\r\n\r\ntype ActiveTourState = {\r\n key: string;\r\n stepIndex: number;\r\n};\r\n\r\ntype BootstrapState = {\r\n experience: RuntimeExperience | null;\r\n survey: RuntimeSurvey | null;\r\n attributes: Record<string, unknown> | null;\r\n loading: boolean;\r\n};\r\n\r\nexport function SunboardProvider({\r\n spec: specProp,\r\n user,\r\n loading: loadingProp,\r\n route,\r\n navigate,\r\n apiUrl,\r\n publishableKey,\r\n identities: identitiesProp,\r\n onTrack,\r\n children\r\n}: SunboardProviderProps) {\r\n const currentRoute = route ?? getBrowserRoute();\r\n const currentOrigin = getBrowserOrigin();\r\n const client = useMemo(() => {\r\n if (!publishableKey) {\r\n return null;\r\n }\r\n\r\n // `apiUrl` is a dev-only override; production apps only supply a key and\r\n // talk to the hosted runtime API by default.\r\n return new SunboardRuntimeClient({\r\n apiKey: publishableKey,\r\n apiUrl: apiUrl || DEFAULT_RUNTIME_API_URL,\r\n sdkVersion: SDK_VERSION\r\n });\r\n }, [apiUrl, publishableKey]);\r\n // Bump to force a bootstrap refetch in-session (e.g. after a survey submit,\r\n // so freshly collected attributes route the user without a navigation).\r\n const [bootstrapNonce, setBootstrapNonce] = useState(0);\r\n const refreshBootstrap = useCallback(() => {\r\n setBootstrapNonce((nonce) => nonce + 1);\r\n }, []);\r\n\r\n const bootstrap = useSunboardBootstrap(client, {\r\n enabled: !specProp,\r\n identities: identitiesProp,\r\n nonce: bootstrapNonce,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n user\r\n });\r\n\r\n const activeExperience = bootstrap.experience;\r\n // A survey pre-empts experiences (the server returns no experiences while one\r\n // is owed). Render the survey's spec when present, otherwise the experience's.\r\n const activeSurvey = bootstrap.survey;\r\n const hostedSpec = activeSurvey?.spec ?? activeExperience?.spec ?? null;\r\n const resolvedSpec = specProp ?? hostedSpec;\r\n const resolvedLoading = loadingProp ?? bootstrap.loading;\r\n const hostedMode = client !== null && !specProp;\r\n\r\n // The user view used for token interpolation/display merges the server-owned\r\n // attributes (the `onboarding.*` namespace returned by bootstrap) into the\r\n // host user, so `{{user.onboarding.*}}` resolves. Network calls keep using\r\n // the original `user` — owned attributes must never be echoed back as host\r\n // properties (which bootstrap overwrites).\r\n const ownedAttributes = bootstrap.attributes;\r\n const effectiveUser = useMemo<SunboardUser | null | undefined>(() => {\r\n if (!user || !ownedAttributes || Object.keys(ownedAttributes).length === 0) {\r\n return user;\r\n }\r\n\r\n return {\r\n ...user,\r\n properties: { ...(user.properties ?? {}), ...ownedAttributes }\r\n };\r\n }, [user, ownedAttributes]);\r\n\r\n const [completedStepKeys, setCompletedStepKeys] = useState<Set<string>>(\r\n () => new Set()\r\n );\r\n const [completedTourKeys, setCompletedTourKeys] = useState<Set<string>>(\r\n () => new Set()\r\n );\r\n const [dismissedTooltipKeys, setDismissedTooltipKeys] = useState<Set<string>>(\r\n () => new Set()\r\n );\r\n const [activeTourState, setActiveTourState] = useState<ActiveTourState | null>(\r\n null\r\n );\r\n // A tooltip the user explicitly asked for (checklist openTooltip action).\r\n // Survives navigation so the tooltip can open once the destination renders.\r\n const [requestedTooltipKey, setRequestedTooltipKey] = useState<string | null>(\r\n null\r\n );\r\n // Session-level dedupe for tooltip \"seen\" reporting.\r\n const seenTooltipKeys = useRef<Set<string>>(new Set());\r\n const lastNavigatedTourStep = useRef<string | null>(null);\r\n const storageKey =\r\n !hostedMode && resolvedSpec && user\r\n ? completedToursStorageKey(resolvedSpec.key, user.id)\r\n : null;\r\n const tooltipStorageKey =\r\n !hostedMode && resolvedSpec && user\r\n ? dismissedTooltipsStorageKey(resolvedSpec.key, user.id)\r\n : null;\r\n const experienceKey =\r\n activeSurvey?.key ?? activeExperience?.key ?? resolvedSpec?.key ?? null;\r\n const versionId =\r\n activeSurvey?.version.id ?? activeExperience?.version.id ?? null;\r\n\r\n useEffect(() => {\r\n setCompletedStepKeys(new Set());\r\n setActiveTourState(null);\r\n setRequestedTooltipKey(null);\r\n seenTooltipKeys.current = new Set();\r\n lastNavigatedTourStep.current = null;\r\n\r\n setCompletedTourKeys(\r\n storageKey ? readStoredKeys(storageKey) : new Set()\r\n );\r\n setDismissedTooltipKeys(\r\n tooltipStorageKey ? readStoredKeys(tooltipStorageKey) : new Set()\r\n );\r\n }, [resolvedSpec?.key, storageKey, tooltipStorageKey, user?.id]);\r\n\r\n useEffect(() => {\r\n if (!hostedMode || !activeExperience) {\r\n return;\r\n }\r\n\r\n applyProgress(\r\n activeExperience.progress,\r\n setCompletedStepKeys,\r\n setCompletedTourKeys,\r\n setDismissedTooltipKeys\r\n );\r\n }, [activeExperience, hostedMode]);\r\n\r\n const effectiveCompletedStepKeys = useMemo(() => {\r\n if (!hostedMode || !activeExperience) {\r\n return completedStepKeys;\r\n }\r\n\r\n return mergeKeys(\r\n completedStepKeys,\r\n activeExperience.progress.completedStepKeys\r\n );\r\n }, [activeExperience, completedStepKeys, hostedMode]);\r\n\r\n const effectiveCompletedTourKeys = useMemo(() => {\r\n if (!hostedMode || !activeExperience) {\r\n return completedTourKeys;\r\n }\r\n\r\n return mergeKeys(\r\n completedTourKeys,\r\n activeExperience.progress.completedTourKeys\r\n );\r\n }, [activeExperience, completedTourKeys, hostedMode]);\r\n\r\n const effectiveDismissedTooltipKeys = useMemo(() => {\r\n if (!hostedMode || !activeExperience) {\r\n return dismissedTooltipKeys;\r\n }\r\n\r\n return mergeKeys(\r\n dismissedTooltipKeys,\r\n activeExperience.progress.dismissedTooltipKeys\r\n );\r\n }, [activeExperience, dismissedTooltipKeys, hostedMode]);\r\n\r\n useEffect(() => {\r\n if (!resolvedSpec) {\r\n return;\r\n }\r\n\r\n const tourCompletedStepKeys = (resolvedSpec.checklist?.steps ?? [])\r\n .filter(\r\n (step) =>\r\n step.completeWhen?.tour &&\r\n effectiveCompletedTourKeys.has(step.completeWhen.tour)\r\n )\r\n .map((step) => step.key);\r\n\r\n if (tourCompletedStepKeys.length === 0) {\r\n return;\r\n }\r\n\r\n setCompletedStepKeys((current) => mergeKeys(current, tourCompletedStepKeys));\r\n }, [effectiveCompletedTourKeys, resolvedSpec]);\r\n\r\n const activeTour = useMemo<ActiveSunboardTour | null>(() => {\r\n if (!resolvedSpec || !activeTourState) {\r\n return null;\r\n }\r\n\r\n const tour = resolvedSpec.tours[activeTourState.key];\r\n const step = tour?.steps[activeTourState.stepIndex];\r\n\r\n if (!tour || !step) {\r\n return null;\r\n }\r\n\r\n return {\r\n key: activeTourState.key,\r\n name: tour.name,\r\n step,\r\n stepIndex: activeTourState.stepIndex,\r\n totalSteps: tour.steps.length\r\n };\r\n }, [activeTourState, resolvedSpec]);\r\n\r\n useEffect(() => {\r\n if (!activeTour?.step.route || routeMatches(activeTour.step.route, currentRoute)) {\r\n return;\r\n }\r\n\r\n const navigationKey = `${activeTour.key}:${activeTour.step.key}:${activeTour.step.route}`;\r\n\r\n if (lastNavigatedTourStep.current === navigationKey) {\r\n return;\r\n }\r\n\r\n lastNavigatedTourStep.current = navigationKey;\r\n void navigateWithFallback(activeTour.step.route, navigate);\r\n }, [activeTour, currentRoute, navigate]);\r\n\r\n // Record per-step tour progress. Fires once per (tour, step) transition;\r\n // the dep array tracks step.key + tour key so re-renders don't spam the\r\n // endpoint and re-entries refresh `lastSeenAt` server-side.\r\n useEffect(() => {\r\n if (\r\n !hostedMode ||\r\n !client ||\r\n !activeTour ||\r\n !user ||\r\n !experienceKey\r\n ) {\r\n return;\r\n }\r\n\r\n void client\r\n .recordTourStep({\r\n experienceKey,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n stepKey: activeTour.step.key,\r\n tourKey: activeTour.key,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined\r\n })\r\n .catch(() => {\r\n // Best-effort tracking — never break the host app on a 4xx/5xx.\r\n });\r\n // Intentionally narrow deps to the tour/step identity. We do not refire\r\n // when `currentRoute` changes mid-step, because the route adapter scrolls\r\n // the same popover into a new viewport, not a new logical step.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [activeTour?.key, activeTour?.step.key, client, currentOrigin, experienceKey, hostedMode, user?.id]);\r\n\r\n const totalSteps = resolvedSpec?.checklist?.steps.length ?? 0;\r\n const completedCount =\r\n resolvedSpec?.checklist?.steps.filter((step) =>\r\n effectiveCompletedStepKeys.has(step.key)\r\n ).length ?? 0;\r\n\r\n const completeTour = useCallback(\r\n (tourKey: string) => {\r\n setCompletedTourKeys((current) => {\r\n const next = new Set(current);\r\n next.add(tourKey);\r\n\r\n if (storageKey) {\r\n writeStoredKeys(storageKey, next);\r\n }\r\n\r\n return next;\r\n });\r\n },\r\n [storageKey]\r\n );\r\n\r\n const track = useCallback<SunboardContextValue[\"track\"]>(\r\n async (eventName, properties) => {\r\n if (resolvedLoading || !resolvedSpec) {\r\n return { ok: false, reason: \"not-ready\" };\r\n }\r\n\r\n if (!user) {\r\n return { ok: false, reason: \"missing-user\" };\r\n }\r\n\r\n const matchingStepKeys = (resolvedSpec.checklist?.steps ?? [])\r\n .filter((step) => step.completeWhen?.event === eventName)\r\n .map((step) => step.key);\r\n\r\n if (matchingStepKeys.length > 0) {\r\n // `mergeKeys` returns `current` unchanged when every key is already\r\n // present, so re-firing an already-completed step's event is a no-op\r\n // and does not churn state/recreate the context value. This is what\r\n // keeps a consumer that calls `track()` from an effect depending on the\r\n // whole context value from looping render→track→render.\r\n setCompletedStepKeys((current) => mergeKeys(current, matchingStepKeys));\r\n }\r\n\r\n let networkOk = true;\r\n\r\n if (hostedMode && client && experienceKey) {\r\n try {\r\n const response = await client.track({\r\n eventName,\r\n experienceKey,\r\n origin: currentOrigin,\r\n properties,\r\n route: currentRoute,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined\r\n });\r\n // A scoped track always returns progress; guard anyway since the\r\n // shared response type allows null for experience-agnostic tracks.\r\n if (response.progress) {\r\n applyProgress(\r\n response.progress,\r\n setCompletedStepKeys,\r\n setCompletedTourKeys,\r\n setDismissedTooltipKeys\r\n );\r\n }\r\n } catch {\r\n networkOk = false;\r\n }\r\n }\r\n\r\n try {\r\n await onTrack?.(eventName, properties);\r\n } catch {\r\n return { ok: false, reason: \"network-error\" };\r\n }\r\n\r\n if (!networkOk) {\r\n return { ok: false, reason: \"network-error\" };\r\n }\r\n\r\n return { ok: true };\r\n },\r\n [\r\n client,\r\n currentRoute,\r\n currentOrigin,\r\n experienceKey,\r\n hostedMode,\r\n onTrack,\r\n resolvedLoading,\r\n resolvedSpec,\r\n user,\r\n versionId\r\n ]\r\n );\r\n\r\n const submitSurvey = useCallback<SunboardContextValue[\"submitSurvey\"]>(\r\n async (answers) => {\r\n if (resolvedLoading || !resolvedSpec) {\r\n return { ok: false, reason: \"not-ready\" };\r\n }\r\n\r\n if (!user) {\r\n return { ok: false, reason: \"missing-user\" };\r\n }\r\n\r\n if (resolvedSpec.type !== \"survey\" || !resolvedSpec.survey) {\r\n return { ok: false, reason: \"not-survey\" };\r\n }\r\n\r\n // Surveys are server-backed: a survey can only be answered when the\r\n // runtime resolved it for this user.\r\n if (!hostedMode || !client || !experienceKey) {\r\n return { ok: false, reason: \"not-ready\" };\r\n }\r\n\r\n try {\r\n await client.submitSurvey({\r\n answers,\r\n experienceKey,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined\r\n });\r\n } catch {\r\n return { ok: false, reason: \"network-error\" };\r\n }\r\n\r\n // Re-bootstrap so the freshly collected attributes route the user now.\r\n refreshBootstrap();\r\n return { ok: true };\r\n },\r\n [\r\n client,\r\n currentOrigin,\r\n currentRoute,\r\n experienceKey,\r\n hostedMode,\r\n refreshBootstrap,\r\n resolvedLoading,\r\n resolvedSpec,\r\n user,\r\n versionId\r\n ]\r\n );\r\n\r\n const navigateToStep = useCallback(\r\n async (step: SunboardStep) => {\r\n if (!step.route) {\r\n return;\r\n }\r\n\r\n await navigateWithFallback(step.route, navigate);\r\n },\r\n [navigate]\r\n );\r\n\r\n const startTour = useCallback<SunboardContextValue[\"startTour\"]>(\r\n async (tourKey) => {\r\n if (resolvedLoading || !resolvedSpec) {\r\n return { ok: false, reason: \"not-ready\" };\r\n }\r\n\r\n if (!user) {\r\n return { ok: false, reason: \"missing-user\" };\r\n }\r\n\r\n if (!resolvedSpec.tours[tourKey]) {\r\n return { ok: false, reason: \"missing-tour\" };\r\n }\r\n\r\n lastNavigatedTourStep.current = null;\r\n setActiveTourState({ key: tourKey, stepIndex: 0 });\r\n\r\n // Fire-and-forget tour-start. The per-step record fires from the\r\n // useEffect below when activeTour resolves, so step[0] gets recorded\r\n // independently. We deliberately don't await — popover UX must not\r\n // block on network.\r\n if (hostedMode && client && experienceKey && user) {\r\n void client\r\n .startTour({\r\n experienceKey,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n tourKey,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined\r\n })\r\n .catch(() => {\r\n // Tracking is best-effort. Network failures shouldn't surface\r\n // to the host app.\r\n });\r\n }\r\n\r\n return { ok: true };\r\n },\r\n [\r\n client,\r\n currentRoute,\r\n currentOrigin,\r\n experienceKey,\r\n hostedMode,\r\n resolvedLoading,\r\n resolvedSpec,\r\n user,\r\n versionId\r\n ]\r\n );\r\n\r\n const dismissTour = useCallback<SunboardContextValue[\"dismissTour\"]>(\r\n async (options) => {\r\n // Capture what was on screen before clearing state — the dismissal\r\n // report needs the tour + step the user actually bailed on.\r\n const dismissed = activeTourState;\r\n const dismissedStepKey = dismissed\r\n ? resolvedSpec?.tours[dismissed.key]?.steps[dismissed.stepIndex]?.key\r\n : undefined;\r\n\r\n setActiveTourState(null);\r\n lastNavigatedTourStep.current = null;\r\n\r\n // Best-effort negative-signal tracking, same contract as startTour:\r\n // never block the UI on network, never surface failures to the host.\r\n // Silent dismissals (missing target etc.) are not user intent and are\r\n // deliberately not reported.\r\n if (\r\n !options?.silent &&\r\n hostedMode &&\r\n client &&\r\n experienceKey &&\r\n user &&\r\n dismissed &&\r\n dismissedStepKey\r\n ) {\r\n void client\r\n .dismissTour({\r\n experienceKey,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n stepKey: dismissedStepKey,\r\n tourKey: dismissed.key,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined\r\n })\r\n .catch(() => {\r\n // Tracking is best-effort.\r\n });\r\n }\r\n\r\n return { ok: true };\r\n },\r\n [\r\n activeTourState,\r\n client,\r\n currentOrigin,\r\n currentRoute,\r\n experienceKey,\r\n hostedMode,\r\n resolvedSpec,\r\n user,\r\n versionId\r\n ]\r\n );\r\n\r\n const finishActiveTour = useCallback(\r\n async (tourKey: string) => {\r\n completeTour(tourKey);\r\n\r\n if (!hostedMode || !client || !experienceKey || !user) {\r\n return;\r\n }\r\n\r\n try {\r\n const response = await client.completeTour({\r\n experienceKey,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n tourKey,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined\r\n });\r\n applyProgress(\r\n response.progress,\r\n setCompletedStepKeys,\r\n setCompletedTourKeys,\r\n setDismissedTooltipKeys\r\n );\r\n } catch {\r\n // Fail soft: keep local optimistic completion, never break the host app.\r\n }\r\n },\r\n [\r\n client,\r\n completeTour,\r\n currentRoute,\r\n currentOrigin,\r\n experienceKey,\r\n hostedMode,\r\n user,\r\n versionId\r\n ]\r\n );\r\n\r\n const advanceTour = useCallback<SunboardContextValue[\"advanceTour\"]>(\r\n async () => {\r\n if (resolvedLoading || !resolvedSpec || !activeTourState) {\r\n return { ok: false, reason: \"not-ready\" };\r\n }\r\n\r\n if (!user) {\r\n return { ok: false, reason: \"missing-user\" };\r\n }\r\n\r\n const tour = resolvedSpec.tours[activeTourState.key];\r\n\r\n if (!tour) {\r\n return { ok: false, reason: \"missing-tour\" };\r\n }\r\n\r\n lastNavigatedTourStep.current = null;\r\n\r\n if (activeTourState.stepIndex >= tour.steps.length - 1) {\r\n const completedKey = activeTourState.key;\r\n setActiveTourState(null);\r\n await finishActiveTour(completedKey);\r\n return { ok: true };\r\n }\r\n\r\n setActiveTourState({\r\n key: activeTourState.key,\r\n stepIndex: activeTourState.stepIndex + 1\r\n });\r\n return { ok: true };\r\n },\r\n [activeTourState, finishActiveTour, resolvedLoading, resolvedSpec, user]\r\n );\r\n\r\n const openTooltip = useCallback<SunboardContextValue[\"openTooltip\"]>(\r\n async (tooltipKey) => {\r\n if (resolvedLoading || !resolvedSpec) {\r\n return { ok: false, reason: \"not-ready\" };\r\n }\r\n\r\n if (!user) {\r\n return { ok: false, reason: \"missing-user\" };\r\n }\r\n\r\n if (!resolvedSpec.tooltips[tooltipKey]) {\r\n return { ok: false, reason: \"missing-tooltip\" };\r\n }\r\n\r\n setRequestedTooltipKey(tooltipKey);\r\n return { ok: true };\r\n },\r\n [resolvedLoading, resolvedSpec, user]\r\n );\r\n\r\n const dismissTooltip = useCallback<SunboardContextValue[\"dismissTooltip\"]>(\r\n async (tooltipKey, options) => {\r\n if (resolvedLoading || !resolvedSpec) {\r\n return { ok: false, reason: \"not-ready\" };\r\n }\r\n\r\n if (!user) {\r\n return { ok: false, reason: \"missing-user\" };\r\n }\r\n\r\n if (!resolvedSpec.tooltips[tooltipKey]) {\r\n return { ok: false, reason: \"missing-tooltip\" };\r\n }\r\n\r\n const via: TooltipDismissVia = options?.via ?? \"close\";\r\n\r\n setRequestedTooltipKey((current) =>\r\n current === tooltipKey ? null : current\r\n );\r\n setDismissedTooltipKeys((current) => {\r\n const next = mergeKeys(current, [tooltipKey]);\r\n\r\n if (tooltipStorageKey && next !== current) {\r\n writeStoredKeys(tooltipStorageKey, next);\r\n }\r\n\r\n return next;\r\n });\r\n\r\n // Best-effort persistence, same contract as tour dismissals: never block\r\n // the UI on network, never surface failures to the host.\r\n if (hostedMode && client && experienceKey) {\r\n void client\r\n .dismissTooltip({\r\n experienceKey,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n tooltipKey,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined,\r\n via\r\n })\r\n .catch(() => {\r\n // Tracking is best-effort.\r\n });\r\n }\r\n\r\n return { ok: true };\r\n },\r\n [\r\n client,\r\n currentOrigin,\r\n currentRoute,\r\n experienceKey,\r\n hostedMode,\r\n resolvedLoading,\r\n resolvedSpec,\r\n tooltipStorageKey,\r\n user,\r\n versionId\r\n ]\r\n );\r\n\r\n const markTooltipSeen = useCallback<SunboardContextValue[\"markTooltipSeen\"]>(\r\n (tooltipKey) => {\r\n if (\r\n !resolvedSpec?.tooltips[tooltipKey] ||\r\n seenTooltipKeys.current.has(tooltipKey)\r\n ) {\r\n return;\r\n }\r\n\r\n seenTooltipKeys.current.add(tooltipKey);\r\n\r\n if (hostedMode && client && experienceKey && user) {\r\n void client\r\n .recordTooltipSeen({\r\n experienceKey,\r\n origin: currentOrigin,\r\n route: currentRoute,\r\n tooltipKey,\r\n user: toRuntimeUser(user),\r\n versionId: versionId ?? undefined\r\n })\r\n .catch(() => {\r\n // Tracking is best-effort.\r\n });\r\n }\r\n },\r\n [\r\n client,\r\n currentOrigin,\r\n currentRoute,\r\n experienceKey,\r\n hostedMode,\r\n resolvedSpec,\r\n user,\r\n versionId\r\n ]\r\n );\r\n\r\n const startChecklistStep = useCallback<\r\n SunboardContextValue[\"startChecklistStep\"]\r\n >(\r\n async (step) => {\r\n if (step.action?.type === \"startTour\") {\r\n return startTour(step.action.tour);\r\n }\r\n\r\n if (step.action?.type === \"openTooltip\") {\r\n // Land the user where the action happens, then open the tooltip on\r\n // the element. The tooltip's own route is the destination of record;\r\n // the step's route is the fallback for an everywhere-tooltip.\r\n const tooltipKey = step.action.tooltip;\r\n const destination =\r\n resolvedSpec?.tooltips[tooltipKey]?.route ?? step.route;\r\n\r\n if (destination && !routeMatches(destination, currentRoute)) {\r\n await navigateWithFallback(destination, navigate);\r\n }\r\n\r\n return openTooltip(tooltipKey);\r\n }\r\n\r\n await navigateToStep(step);\r\n },\r\n [\r\n currentRoute,\r\n navigate,\r\n navigateToStep,\r\n openTooltip,\r\n resolvedSpec,\r\n startTour\r\n ]\r\n );\r\n\r\n const visibleHotspots = useMemo<SunboardContextValue[\"visibleHotspots\"]>(() => {\r\n if (!resolvedSpec || activeTour) {\r\n return [];\r\n }\r\n\r\n return Object.entries(resolvedSpec.hotspots)\r\n .filter(([, hotspot]) => routeMatches(hotspot.route, currentRoute))\r\n .filter(\r\n ([, hotspot]) =>\r\n hotspot.repeatable ||\r\n !effectiveCompletedTourKeys.has(hotspot.action.tour)\r\n )\r\n .map(([key, hotspot]) => ({ key, hotspot }));\r\n }, [activeTour, effectiveCompletedTourKeys, currentRoute, resolvedSpec]);\r\n\r\n const visibleTooltips = useMemo<SunboardContextValue[\"visibleTooltips\"]>(() => {\r\n if (!resolvedSpec || activeTour) {\r\n return [];\r\n }\r\n\r\n const requestedTooltip = requestedTooltipKey\r\n ? resolvedSpec.tooltips[requestedTooltipKey]\r\n : undefined;\r\n\r\n if (requestedTooltipKey && requestedTooltip) {\r\n // The user asked for this one. While it waits for its route (navigation\r\n // in flight), nothing else competes for the slot; a prior dismissal\r\n // does not suppress an explicit request.\r\n if (!routeMatches(requestedTooltip.route, currentRoute)) {\r\n return [];\r\n }\r\n\r\n return [\r\n { key: requestedTooltipKey, requested: true, tooltip: requestedTooltip }\r\n ];\r\n }\r\n\r\n return Object.entries(resolvedSpec.tooltips)\r\n .filter(\r\n ([key, tooltip]) =>\r\n routeMatches(tooltip.route, currentRoute) &&\r\n !effectiveDismissedTooltipKeys.has(key)\r\n )\r\n .map(([key, tooltip]) => ({ key, requested: false, tooltip }));\r\n }, [\r\n activeTour,\r\n currentRoute,\r\n effectiveDismissedTooltipKeys,\r\n requestedTooltipKey,\r\n resolvedSpec\r\n ]);\r\n\r\n const value = useMemo<SunboardContextValue>(\r\n () => ({\r\n spec: resolvedSpec,\r\n user: effectiveUser,\r\n loading: resolvedLoading,\r\n route: currentRoute,\r\n completedStepKeys: effectiveCompletedStepKeys,\r\n completedTourKeys: effectiveCompletedTourKeys,\r\n completedCount,\r\n totalSteps,\r\n activeTour,\r\n track,\r\n submitSurvey,\r\n startTour,\r\n dismissTour,\r\n advanceTour,\r\n navigateToStep,\r\n startChecklistStep,\r\n visibleHotspots,\r\n dismissedTooltipKeys: effectiveDismissedTooltipKeys,\r\n visibleTooltips,\r\n openTooltip,\r\n dismissTooltip,\r\n markTooltipSeen\r\n }),\r\n [\r\n resolvedSpec,\r\n effectiveUser,\r\n resolvedLoading,\r\n currentRoute,\r\n effectiveCompletedStepKeys,\r\n effectiveCompletedTourKeys,\r\n completedCount,\r\n totalSteps,\r\n activeTour,\r\n track,\r\n submitSurvey,\r\n startTour,\r\n dismissTour,\r\n advanceTour,\r\n navigateToStep,\r\n startChecklistStep,\r\n visibleHotspots,\r\n effectiveDismissedTooltipKeys,\r\n visibleTooltips,\r\n openTooltip,\r\n dismissTooltip,\r\n markTooltipSeen\r\n ]\r\n );\r\n\r\n return (\r\n <SunboardContext.Provider value={value}>\r\n {children}\r\n </SunboardContext.Provider>\r\n );\r\n}\r\n\r\nfunction useSunboardBootstrap(\r\n client: SunboardRuntimeClient | null,\r\n options: {\r\n enabled: boolean;\r\n identities?: IdentitiesProp;\r\n nonce: number;\r\n origin?: string;\r\n route: string;\r\n user?: SunboardUser | null;\r\n }\r\n): BootstrapState {\r\n const { enabled, identities: identitiesProp, nonce, origin, route, user } =\r\n options;\r\n // Read through a ref so the effect always sees the latest prop without the\r\n // object's identity churning the dep array (mirrors the `user` handling:\r\n // identities influence the next bootstrap, they don't trigger one).\r\n const identitiesRef = useRef(identitiesProp);\r\n identitiesRef.current = identitiesProp;\r\n const [state, setState] = useState<BootstrapState>({\r\n attributes: null,\r\n experience: null,\r\n loading: false,\r\n survey: null\r\n });\r\n const warnedSdkAdvisory = useRef(false);\r\n\r\n useEffect(() => {\r\n if (!client || !enabled || !user?.id) {\r\n setState({\r\n attributes: null,\r\n experience: null,\r\n loading: false,\r\n survey: null\r\n });\r\n return;\r\n }\r\n\r\n // TODO: plumb `controller.signal` through SunboardRuntimeClient.post into\r\n // the underlying fetch so aborted requests cancel at the network layer. In\r\n // React 19 dev StrictMode the provider mounts twice; the first effect's\r\n // fetch keeps flying and can surface as `TypeError: Failed to fetch` in the\r\n // browser console. Harmless (we guard with signal.aborted before touching\r\n // state) but noisy in dev.\r\n const controller = new AbortController();\r\n setState({\r\n attributes: null,\r\n experience: null,\r\n loading: true,\r\n survey: null\r\n });\r\n\r\n // Providers are probed at request time, on every bootstrap: the id can\r\n // change when an anonymous visitor identifies, and a snippet that loaded\r\n // late is picked up by the next bootstrap.\r\n const identities = resolveIdentities(identitiesRef.current);\r\n\r\n client\r\n .bootstrap({\r\n origin,\r\n route,\r\n user: toRuntimeUser(user),\r\n ...(identities ? { identities } : {})\r\n })\r\n .then((response) => {\r\n if (controller.signal.aborted) {\r\n return;\r\n }\r\n\r\n if (\r\n response.experiences.length === 0 &&\r\n !response.survey &&\r\n isDevEnvironment()\r\n ) {\r\n console.warn(\r\n `[sunboard] bootstrap returned no active experiences for route \"${route}\". Check the dashboard for an enabled deployment whose trigger.route matches (the publishable key selects the environment).`\r\n );\r\n }\r\n\r\n if (\r\n response.sdkAdvisory &&\r\n isDevEnvironment() &&\r\n !warnedSdkAdvisory.current\r\n ) {\r\n warnedSdkAdvisory.current = true;\r\n console.warn(`[sunboard] ${response.sdkAdvisory.message}`);\r\n }\r\n\r\n setState({\r\n attributes: response.attributes ?? null,\r\n experience: response.experiences[0] ?? null,\r\n loading: false,\r\n survey: response.survey ?? null\r\n });\r\n })\r\n .catch((error: unknown) => {\r\n if (controller.signal.aborted) {\r\n return;\r\n }\r\n\r\n if (isDevEnvironment()) {\r\n console.warn(\"[sunboard] bootstrap failed\", error);\r\n }\r\n\r\n setState({\r\n attributes: null,\r\n experience: null,\r\n loading: false,\r\n survey: null\r\n });\r\n });\r\n\r\n // The bootstrap often races the analytics snippet: posthog-js typically\r\n // initializes in a parent effect that runs after this one, so the probe\r\n // above finds nothing on the first (and usually only) bootstrap of a\r\n // session. Keep probing briefly and push a late capture through\r\n // `identify`; the server merges it per provider key, same as bootstrap.\r\n const cancelLateCapture = scheduleLateIdentityCapture({\r\n client,\r\n identitiesRef,\r\n origin,\r\n sent: identities,\r\n user\r\n });\r\n\r\n return () => {\r\n controller.abort();\r\n cancelLateCapture();\r\n };\r\n // user.id is the identity key; we deliberately do not refetch when the\r\n // user object reference changes without an id change. `route` is\r\n // intentionally excluded so navigation does not wipe the active experience\r\n // — per-route visibility is handled by the runtime via step/tour/hotspot\r\n // route matching.\r\n // `nonce` is included so an explicit refreshBootstrap() (e.g. after a survey\r\n // submit) refetches even though client/user/route are unchanged.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [client, enabled, nonce, origin, user?.id]);\r\n\r\n return state;\r\n}\r\n\r\n/** Late-capture probe cadence: ~10s of 1s ticks covers slow analytics init. */\r\nconst IDENTITY_CAPTURE_INTERVAL_MS = 1_000;\r\nconst IDENTITY_CAPTURE_MAX_ATTEMPTS = 10;\r\n\r\n/**\r\n * Keep probing for analytics-provider ids the bootstrap missed, and push the\r\n * first find through `identify`. One send per bootstrap: after a successful\r\n * capture the poll stops; the next bootstrap (new user/session) starts fresh.\r\n */\r\nfunction scheduleLateIdentityCapture(options: {\r\n client: SunboardRuntimeClient;\r\n identitiesRef: { readonly current: IdentitiesProp | undefined };\r\n origin?: string;\r\n sent: Record<string, string> | undefined;\r\n user: SunboardUser;\r\n}): () => void {\r\n const { client, identitiesRef, origin, sent, user } = options;\r\n let attempts = 0;\r\n\r\n const timer = setInterval(() => {\r\n attempts += 1;\r\n\r\n // Read the prop through the ref on every tick so flipping it to `false`\r\n // mid-poll stops the capture.\r\n if (identitiesRef.current === false) {\r\n clearInterval(timer);\r\n return;\r\n }\r\n\r\n const detected = resolveIdentities(identitiesRef.current);\r\n if (!detected || !hasUnsentIdentity(detected, sent)) {\r\n if (attempts >= IDENTITY_CAPTURE_MAX_ATTEMPTS) {\r\n clearInterval(timer);\r\n }\r\n return;\r\n }\r\n\r\n clearInterval(timer);\r\n client\r\n .identify({\r\n identities: detected,\r\n user: toRuntimeUser(user),\r\n ...(origin ? { origin } : {})\r\n })\r\n .catch((error: unknown) => {\r\n if (isDevEnvironment()) {\r\n console.warn(\"[sunboard] late identity capture failed\", error);\r\n }\r\n });\r\n }, IDENTITY_CAPTURE_INTERVAL_MS);\r\n\r\n return () => clearInterval(timer);\r\n}\r\n\r\nfunction hasUnsentIdentity(\r\n detected: Record<string, string>,\r\n sent: Record<string, string> | undefined\r\n): boolean {\r\n return Object.entries(detected).some(([provider, id]) => sent?.[provider] !== id);\r\n}\r\n\r\nfunction applyProgress(\r\n progress: RuntimeProgress,\r\n setSteps: Dispatch<SetStateAction<Set<string>>>,\r\n setTours: Dispatch<SetStateAction<Set<string>>>,\r\n setDismissedTooltips?: Dispatch<SetStateAction<Set<string>>>\r\n) {\r\n setSteps((current) => mergeKeys(current, progress.completedStepKeys));\r\n setTours((current) => mergeKeys(current, progress.completedTourKeys));\r\n setDismissedTooltips?.((current) =>\r\n mergeKeys(current, progress.dismissedTooltipKeys)\r\n );\r\n}\r\n\r\nfunction mergeKeys(current: Set<string>, incoming: string[]): Set<string> {\r\n if (incoming.length === 0) {\r\n return current;\r\n }\r\n\r\n const next = new Set(current);\r\n\r\n for (const key of incoming) {\r\n next.add(key);\r\n }\r\n\r\n if (next.size === current.size) {\r\n return current;\r\n }\r\n\r\n return next;\r\n}\r\n\r\nfunction isDevEnvironment(): boolean {\r\n if (typeof process === \"undefined\" || !process.env) {\r\n return false;\r\n }\r\n\r\n return process.env.NODE_ENV !== \"production\";\r\n}\r\n\r\nfunction toRuntimeUser(user: SunboardUser) {\r\n return {\r\n email: user.email ?? undefined,\r\n group: user.group ?? undefined,\r\n id: user.id,\r\n properties: user.properties\r\n };\r\n}\r\n\r\nfunction completedToursStorageKey(specKey: string, userId: string): string {\r\n return `sunboard:${specKey}:${userId}:completed-tours`;\r\n}\r\n\r\nfunction dismissedTooltipsStorageKey(specKey: string, userId: string): string {\r\n return `sunboard:${specKey}:${userId}:dismissed-tooltips`;\r\n}\r\n\r\nfunction getBrowserRoute(): string {\r\n if (typeof window === \"undefined\") {\r\n return \"/\";\r\n }\r\n\r\n return `${window.location.pathname}${window.location.search}`;\r\n}\r\n\r\nfunction getBrowserOrigin(): string | undefined {\r\n if (typeof window === \"undefined\") {\r\n return undefined;\r\n }\r\n\r\n return window.location.origin;\r\n}\r\n\r\nfunction readStoredKeys(storageKey: string): Set<string> {\r\n if (typeof window === \"undefined\") {\r\n return new Set();\r\n }\r\n\r\n try {\r\n const rawValue = window.localStorage.getItem(storageKey);\r\n const parsed = rawValue ? (JSON.parse(rawValue) as unknown) : [];\r\n\r\n if (!Array.isArray(parsed)) {\r\n return new Set();\r\n }\r\n\r\n return new Set(parsed.filter((value): value is string => typeof value === \"string\"));\r\n } catch {\r\n return new Set();\r\n }\r\n}\r\n\r\nfunction writeStoredKeys(storageKey: string, keys: Set<string>) {\r\n if (typeof window === \"undefined\") {\r\n return;\r\n }\r\n\r\n try {\r\n window.localStorage.setItem(storageKey, JSON.stringify([...keys].sort()));\r\n } catch {\r\n // Ignore storage failures; persistence should never break the host app.\r\n }\r\n}\r\n","import type { SunboardUser } from \"../hooks/sunboard-context\";\n\nconst TOKEN_PATTERN =\n /\\{\\{\\s*user\\.([\\w][\\w.]*)(?:\\s*\\|\\s*(?:\"([^\"]*)\"|'([^']*)'))?\\s*\\}\\}/g;\n\ntype FlatUser = Record<string, unknown>;\n\nfunction flattenUser(user: SunboardUser | null | undefined): FlatUser {\n if (!user) return {};\n const properties = (user.properties ?? {}) as Record<string, unknown>;\n const flat: FlatUser = { ...properties };\n flat.id = user.id;\n if (user.email !== undefined) flat.email = user.email;\n if (user.group !== undefined) flat.group = user.group;\n return flat;\n}\n\nfunction lookupPath(root: FlatUser, path: string): unknown {\n const segments = path.split(\".\");\n let current: unknown = root;\n for (const segment of segments) {\n if (current === null || current === undefined) return undefined;\n if (typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[segment];\n }\n return current;\n}\n\nfunction coerceValue(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === \"string\") return value.length > 0 ? value : null;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n return null;\n}\n\nexport function interpolateTokens(\n template: string,\n user: SunboardUser | null | undefined\n): string {\n if (!template || !template.includes(\"{{\")) return template;\n const flat = flattenUser(user);\n return template.replace(\n TOKEN_PATTERN,\n (_match, path: string, doubleQuoted?: string, singleQuoted?: string) => {\n const resolved = coerceValue(lookupPath(flat, path));\n if (resolved !== null) return resolved;\n const fallback = doubleQuoted ?? singleQuoted;\n return fallback ?? \"\";\n }\n );\n}\n\nexport function extractUserTokens(template: string): string[] {\n if (!template || !template.includes(\"{{\")) return [];\n const paths: string[] = [];\n TOKEN_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = TOKEN_PATTERN.exec(template)) !== null) {\n const path = match[1];\n if (path) paths.push(path);\n }\n return paths;\n}\n","import { useContext } from \"react\";\nimport { SunboardContext, type SunboardContextValue } from \"./sunboard-context\";\n\nexport function useSunboard(): SunboardContextValue {\n const context = useContext(SunboardContext);\n\n if (!context) {\n throw new Error(\"useSunboard must be used within SunboardProvider.\");\n }\n\n return context;\n}\n","import { useCallback, useMemo, useRef, useState } from \"react\";\r\nimport type { SunboardStep } from \"@sunboard/core\";\r\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\r\nimport { useSunboard } from \"./useSunboard\";\r\n\r\nexport type ChecklistViewStep = {\r\n key: string;\r\n title: string;\r\n description?: string;\r\n badge?: string;\r\n actionLabel?: string;\r\n};\r\n\r\nexport type ChecklistCompletion = {\r\n title: string;\r\n subtitle?: string;\r\n message?: string;\r\n dismissAfterMs?: number;\r\n};\r\n\r\nexport type UseChecklistResult = {\r\n visible: boolean;\r\n title?: string;\r\n subtitle?: string;\r\n steps: ChecklistViewStep[];\r\n completedStepKeys: ReadonlySet<string>;\r\n completedCount: number;\r\n totalSteps: number;\r\n remainingCount: number;\r\n progress: number;\r\n isCompleted: (key: string) => boolean;\r\n hasAction: (step: ChecklistViewStep) => boolean;\r\n startStep: (step: ChecklistViewStep) => void;\r\n completion?: ChecklistCompletion;\r\n dismissCompletion: () => void;\r\n};\r\n\r\nfunction actionLabelFor(step: SunboardStep): string | undefined {\r\n if (step.action?.type === \"startTour\") return \"Start\";\r\n if (step.action?.type === \"openTooltip\") return \"Show me\";\r\n if (step.route) return \"Open\";\r\n return undefined;\r\n}\r\n\r\nfunction interpolateOptional(\r\n text: string | undefined,\r\n user: Parameters<typeof interpolateTokens>[1]\r\n): string | undefined {\r\n if (!text) return undefined;\r\n const interpolated = interpolateTokens(text, user);\r\n return interpolated || undefined;\r\n}\r\n\r\nexport function useChecklist(): UseChecklistResult {\r\n const {\r\n spec,\r\n user,\r\n loading,\r\n completedStepKeys,\r\n completedCount,\r\n totalSteps,\r\n startChecklistStep\r\n } = useSunboard();\r\n\r\n const [dismissedKey, setDismissedKey] = useState<string | null>(null);\r\n\r\n // A guide renders whatever surfaces it actually declares, so the checklist\r\n // shows whenever the spec carries checklist steps — not based on the type.\r\n // (Surveys deliver through their own surface and never carry a checklist.)\r\n const hasChecklist = Boolean(spec && spec.checklist?.steps.length);\r\n const allCompleted = totalSteps > 0 && completedCount >= totalSteps;\r\n const ready = !loading && !!user && !!spec && hasChecklist;\r\n\r\n // The completion screen celebrates the *transition* from incomplete to\r\n // complete in this session — not the steady \"already done\" state. The server\r\n // already knows per-user completion via bootstrap, so a user who finished\r\n // on another device sees `allCompleted === true` from the first ready\r\n // render and we suppress the celebration. We only show it if we observed\r\n // them being incomplete first.\r\n //\r\n // Scoped to (spec, user) so switching either restarts the observation.\r\n const specKey = spec?.key ?? null;\r\n const userId = user?.id ?? null;\r\n const observationKey =\r\n ready && specKey && userId ? `${specKey}:${userId}` : null;\r\n const observationRef = useRef<{ key: string; sawIncomplete: boolean } | null>(\r\n null\r\n );\r\n if (observationKey && observationRef.current?.key !== observationKey) {\r\n observationRef.current = {\r\n key: observationKey,\r\n sawIncomplete: !allCompleted\r\n };\r\n } else if (observationKey && observationRef.current && !allCompleted) {\r\n observationRef.current.sawIncomplete = true;\r\n }\r\n const sawIncompleteFirst = observationRef.current?.sawIncomplete ?? false;\r\n\r\n const dismissed = dismissedKey !== null && dismissedKey === observationKey;\r\n\r\n const rawSteps = useMemo<SunboardStep[]>(\r\n () => spec?.checklist?.steps ?? [],\r\n [spec]\r\n );\r\n\r\n const title = useMemo(\r\n () => interpolateOptional(spec?.title, user ?? null),\r\n [spec?.title, user]\r\n );\r\n\r\n const subtitle = useMemo(\r\n () => interpolateOptional(spec?.subtitle, user ?? null),\r\n [spec?.subtitle, user]\r\n );\r\n\r\n const completion = useMemo<ChecklistCompletion | undefined>(() => {\r\n const raw = spec?.completion;\r\n if (!raw) return undefined;\r\n const interpolatedTitle = interpolateOptional(raw.title, user ?? null);\r\n if (!interpolatedTitle) return undefined;\r\n return {\r\n title: interpolatedTitle,\r\n subtitle: interpolateOptional(raw.subtitle, user ?? null),\r\n message: interpolateOptional(raw.message, user ?? null),\r\n dismissAfterMs: raw.dismissAfterMs\r\n };\r\n }, [spec?.completion, user]);\r\n\r\n const showingCompletion =\r\n ready && allCompleted && !!completion && sawIncompleteFirst && !dismissed;\r\n const visible = (ready && !allCompleted) || showingCompletion;\r\n\r\n const steps = useMemo<ChecklistViewStep[]>(() => {\r\n return rawSteps.map((step) => ({\r\n key: step.key,\r\n title: interpolateTokens(step.title, user ?? null),\r\n description: step.description\r\n ? interpolateTokens(step.description, user ?? null)\r\n : undefined,\r\n badge: step.badge\r\n ? interpolateTokens(step.badge, user ?? null)\r\n : undefined,\r\n actionLabel: actionLabelFor(step)\r\n }));\r\n }, [rawSteps, user]);\r\n\r\n const stepsByKey = useMemo(() => {\r\n const map = new Map<string, SunboardStep>();\r\n for (const step of rawSteps) {\r\n map.set(step.key, step);\r\n }\r\n return map;\r\n }, [rawSteps]);\r\n\r\n const remainingCount = Math.max(totalSteps - completedCount, 0);\r\n const progress =\r\n totalSteps > 0 ? Math.round((completedCount / totalSteps) * 100) : 0;\r\n\r\n const dismissCompletion = useCallback(() => {\r\n if (observationKey) {\r\n setDismissedKey(observationKey);\r\n }\r\n }, [observationKey]);\r\n\r\n return {\r\n visible,\r\n title,\r\n subtitle,\r\n steps,\r\n completedStepKeys,\r\n completedCount,\r\n totalSteps,\r\n remainingCount,\r\n progress,\r\n isCompleted: (key) => completedStepKeys.has(key),\r\n hasAction: (step) => step.actionLabel !== undefined,\r\n startStep: (step) => {\r\n const raw = stepsByKey.get(step.key);\r\n if (raw) {\r\n void startChecklistStep(raw);\r\n }\r\n },\r\n completion: showingCompletion ? completion : undefined,\r\n dismissCompletion\r\n };\r\n}\r\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { SurveyQuestionType } from \"@sunboard/core\";\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\nimport { useSunboard } from \"./useSunboard\";\n\nexport type SurveyViewOption = {\n value: string;\n label: string;\n};\n\nexport type SurveyViewQuestion = {\n key: string;\n prompt: string;\n type: SurveyQuestionType;\n required: boolean;\n options: SurveyViewOption[];\n};\n\nexport type SurveyViewStep = {\n title?: string;\n subtitle?: string;\n questions: SurveyViewQuestion[];\n};\n\nexport type UseSurveyResult = {\n visible: boolean;\n title?: string;\n subtitle?: string;\n /** Every question across all steps — for all-at-once renderers (the built-in modal). */\n questions: SurveyViewQuestion[];\n /** The authored step grouping (one question per step by default). */\n steps: SurveyViewStep[];\n /** Hook-owned navigation. */\n stepIndex: number;\n totalSteps: number;\n currentStep?: SurveyViewStep;\n progress: number; // 0..100, by step\n isFirstStep: boolean;\n isLastStep: boolean;\n /** True when the current step's required questions are answered. */\n canAdvance: boolean;\n /** Advance to the next step, or submit on the last step. */\n next: () => void;\n back: () => void;\n answers: Record<string, unknown>;\n setAnswer: (key: string, value: string) => void;\n toggleAnswer: (key: string, value: string) => void;\n isAnswered: (key: string) => boolean;\n /** True when every required question (across all steps) is answered. */\n canSubmit: boolean;\n submitting: boolean;\n error: string | null;\n submit: () => Promise<void>;\n};\n\nfunction interpolateOptional(\n text: string | undefined,\n user: Parameters<typeof interpolateTokens>[1]\n): string | undefined {\n if (!text) return undefined;\n const interpolated = interpolateTokens(text, user);\n return interpolated || undefined;\n}\n\nfunction answered(value: unknown): boolean {\n if (value === undefined || value === null) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nexport function useSurvey(): UseSurveyResult {\n const { spec, user, loading, submitSurvey } = useSunboard();\n\n const [answers, setAnswers] = useState<Record<string, unknown>>({});\n const [submitting, setSubmitting] = useState(false);\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [stepIndex, setStepIndex] = useState(0);\n\n const isSurvey = Boolean(spec && spec.type === \"survey\" && spec.survey);\n const ready = !loading && !!user && isSurvey;\n\n const title = useMemo(\n () => interpolateOptional(spec?.title, user ?? null),\n [spec?.title, user]\n );\n const subtitle = useMemo(\n () => interpolateOptional(spec?.subtitle, user ?? null),\n [spec?.subtitle, user]\n );\n\n const steps = useMemo<SurveyViewStep[]>(() => {\n const rawSteps = spec?.survey?.steps ?? [];\n return rawSteps.map((step) => ({\n title: interpolateOptional(step.title, user ?? null),\n subtitle: interpolateOptional(step.subtitle, user ?? null),\n questions: step.questions.map((question) => ({\n key: question.key,\n prompt: interpolateTokens(question.prompt, user ?? null),\n type: question.type,\n required: question.required,\n options: (question.options ?? []).map((option) => ({\n value: option.value,\n label: interpolateTokens(option.label, user ?? null)\n }))\n }))\n }));\n }, [spec?.survey, user]);\n\n const questions = useMemo<SurveyViewQuestion[]>(\n () => steps.flatMap((step) => step.questions),\n [steps]\n );\n\n // Restart at the first step whenever the question set changes (a new survey).\n const signature = questions.map((question) => question.key).join(\"|\");\n const signatureRef = useRef(signature);\n if (signatureRef.current !== signature) {\n signatureRef.current = signature;\n if (stepIndex !== 0) setStepIndex(0);\n }\n\n const totalSteps = steps.length;\n const safeIndex =\n totalSteps > 0 ? Math.min(stepIndex, totalSteps - 1) : 0;\n const currentStep = steps[safeIndex];\n\n const setAnswer = useCallback((key: string, value: string) => {\n setAnswers((current) => ({ ...current, [key]: value }));\n }, []);\n\n const toggleAnswer = useCallback((key: string, value: string) => {\n setAnswers((current) => {\n const existing = Array.isArray(current[key])\n ? (current[key] as string[])\n : [];\n const next = existing.includes(value)\n ? existing.filter((item) => item !== value)\n : [...existing, value];\n return { ...current, [key]: next };\n });\n }, []);\n\n const isAnswered = useCallback(\n (key: string) => answered(answers[key]),\n [answers]\n );\n\n const canSubmit = useMemo(() => {\n if (!ready || submitting) return false;\n return questions\n .filter((question) => question.required)\n .every((question) => answered(answers[question.key]));\n }, [answers, questions, ready, submitting]);\n\n const canAdvance = useMemo(() => {\n if (!currentStep) return false;\n return currentStep.questions\n .filter((question) => question.required)\n .every((question) => answered(answers[question.key]));\n }, [answers, currentStep]);\n\n const submit = useCallback(async () => {\n if (!canSubmit) return;\n setSubmitting(true);\n setError(null);\n\n const result = await submitSurvey(answers);\n setSubmitting(false);\n\n if (result.ok) {\n setSubmitted(true);\n return;\n }\n\n setError(\n result.reason === \"network-error\"\n ? \"Something went wrong. Please try again.\"\n : \"Survey is not ready.\"\n );\n }, [answers, canSubmit, submitSurvey]);\n\n const isLastStep = totalSteps === 0 || safeIndex >= totalSteps - 1;\n const isFirstStep = safeIndex === 0;\n\n const next = useCallback(() => {\n if (isLastStep) {\n void submit();\n } else {\n setStepIndex(safeIndex + 1);\n }\n }, [isLastStep, safeIndex, submit]);\n\n const back = useCallback(() => {\n setStepIndex((current) => Math.max(0, current - 1));\n }, []);\n\n return {\n visible: ready && !submitted,\n title,\n subtitle,\n questions,\n steps,\n stepIndex: safeIndex,\n totalSteps,\n currentStep,\n progress: totalSteps > 0 ? ((safeIndex + 1) / totalSteps) * 100 : 0,\n isFirstStep,\n isLastStep,\n canAdvance,\n next,\n back,\n answers,\n setAnswer,\n toggleAnswer,\n isAnswered,\n canSubmit,\n submitting,\n error,\n submit\n };\n}\n","\nexport const CheckIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-check-icon lucide-check\"><path d=\"M20 6 9 17l-5-5\" /></svg>\n}\n\nexport const ListIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-list-icon lucide-list\"><path d=\"M3 12h.01\" /><path d=\"M3 18h.01\" /><path d=\"M3 6h.01\" /><path d=\"M8 12h13\" /><path d=\"M8 18h13\" /><path d=\"M8 6h13\" /></svg>\n}\n\nexport const XIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-x-icon lucide-x\"><path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" /></svg>\n}\n\nexport const ChevronRightIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-right-icon lucide-chevron-right\"><path d=\"m9 18 6-6-6-6\" /></svg>\n}\n","import { CheckIcon, ChevronRightIcon } from \"../icons\";\nimport type { ChecklistViewStep } from \"../hooks/useChecklist\";\n\ntype ChecklistStepProps = {\n completed: boolean;\n onStart: () => void;\n step: ChecklistViewStep;\n};\n\nexport function ChecklistStep({\n completed,\n onStart,\n step\n}: ChecklistStepProps) {\n const hasAction = step.actionLabel !== undefined;\n\n const status = (\n <span\n className=\"sunboard-step-status\"\n aria-label={completed ? \"Completed\" : \"Incomplete\"}\n >\n {completed ? <CheckIcon /> : null}\n </span>\n );\n\n const content = (\n <div className=\"sunboard-step-content\">\n <div className=\"sunboard-step-heading\">\n <h3 className=\"sunboard-step-title\">{step.title}</h3>\n {step.badge ? (\n <span className=\"sunboard-step-badge\">{step.badge}</span>\n ) : null}\n </div>\n\n {step.description ? (\n <p className=\"sunboard-step-description\">{step.description}</p>\n ) : null}\n </div>\n );\n\n return (\n <li className=\"sunboard-step\" data-completed={completed}>\n {hasAction ? (\n <button\n className=\"sunboard-step-row\"\n type=\"button\"\n onClick={onStart}\n aria-label={`${step.actionLabel} ${step.title}`}\n >\n {status}\n {content}\n <span className=\"sunboard-step-arrow\" aria-hidden=\"true\">\n <ChevronRightIcon />\n </span>\n </button>\n ) : (\n <div className=\"sunboard-step-row\">\n {status}\n {content}\n </div>\n )}\n </li>\n );\n}\n","import { useMemo } from \"react\";\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\nimport { useSunboard } from \"./useSunboard\";\n\nexport function useInterpolated(text: string): string;\nexport function useInterpolated(text: string | undefined): string | undefined;\nexport function useInterpolated(text: string | undefined): string | undefined {\n const { user } = useSunboard();\n return useMemo(() => {\n if (text === undefined) return undefined;\n return interpolateTokens(text, user);\n }, [text, user]);\n}\n","import { useEffect, useState } from \"react\";\n\nexport type TargetElementState = {\n element: HTMLElement | null;\n timedOut: boolean;\n};\n\nexport function useTargetElement(\n selector: string | null,\n enabled: boolean,\n timeoutMs = 5_000\n): TargetElementState {\n const [state, setState] = useState<TargetElementState>({\n element: null,\n timedOut: false\n });\n\n useEffect(() => {\n if (!enabled || !selector) {\n setState({ element: null, timedOut: false });\n return;\n }\n\n let cancelled = false;\n const startedAt = Date.now();\n\n const findTarget = () => {\n if (cancelled) {\n return;\n }\n\n const element = document.querySelector(selector);\n\n if (element instanceof HTMLElement) {\n setState({ element, timedOut: false });\n return;\n }\n\n if (Date.now() - startedAt >= timeoutMs) {\n setState({ element: null, timedOut: true });\n return;\n }\n\n window.setTimeout(findTarget, 100);\n };\n\n setState({ element: null, timedOut: false });\n findTarget();\n\n return () => {\n cancelled = true;\n };\n }, [enabled, selector, timeoutMs]);\n\n return state;\n}\n","import {\r\n FloatingPortal,\r\n autoUpdate,\r\n offset,\r\n useFloating\r\n} from \"@floating-ui/react\";\r\nimport { useEffect } from \"react\";\r\nimport type { SunboardHotspot } from \"@sunboard/core\";\r\nimport { useInterpolated } from \"../hooks/useInterpolated\";\r\nimport { useSunboard } from \"../hooks/useSunboard\";\r\nimport { useTargetElement } from \"../hooks/useTargetElement\";\r\n\r\ntype HotspotProps = {\r\n hotspot: SunboardHotspot;\r\n hotspotKey: string;\r\n};\r\n\r\nconst HOTSPOT_RADIUS_PX = 9;\r\n\r\nexport function SunboardHotspots() {\r\n const { visibleHotspots } = useSunboard();\r\n\r\n return (\r\n <>\r\n {visibleHotspots.map(({ key, hotspot }) => (\r\n <Hotspot hotspot={hotspot} hotspotKey={key} key={key} />\r\n ))}\r\n </>\r\n );\r\n}\r\n\r\nfunction Hotspot({ hotspot, hotspotKey }: HotspotProps) {\r\n const { startTour } = useSunboard();\r\n const label = useInterpolated(hotspot.label);\r\n const target = useTargetElement(hotspot.target, true);\r\n const { refs, floatingStyles } = useFloating({\r\n placement: hotspot.placement,\r\n strategy: \"fixed\",\r\n middleware: [offset(-HOTSPOT_RADIUS_PX)],\r\n whileElementsMounted: autoUpdate\r\n });\r\n\r\n useEffect(() => {\r\n if (!target.element) {\r\n return;\r\n }\r\n\r\n refs.setReference(target.element);\r\n }, [refs, target.element]);\r\n\r\n if (!target.element) {\r\n return null;\r\n }\r\n\r\n return (\r\n <FloatingPortal>\r\n <button\r\n aria-label={label ?? `Start ${hotspot.action.tour} tour`}\r\n className=\"sunboard-hotspot\"\r\n data-pulse={hotspot.pulse}\r\n data-sunboard-hotspot={hotspotKey}\r\n onClick={() => {\r\n void startTour(hotspot.action.tour);\r\n }}\r\n ref={refs.setFloating}\r\n style={floatingStyles}\r\n type=\"button\"\r\n >\r\n <span className=\"sunboard-hotspot-dot\" />\r\n </button>\r\n </FloatingPortal>\r\n );\r\n}\r\n","import {\n arrow,\n autoUpdate,\n FloatingArrow,\n FloatingPortal,\n flip,\n offset,\n shift,\n useFloating\n} from \"@floating-ui/react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { SunboardTooltip } from \"@sunboard/core\";\nimport { useInterpolated } from \"../hooks/useInterpolated\";\nimport { useSunboard } from \"../hooks/useSunboard\";\nimport { useTargetElement } from \"../hooks/useTargetElement\";\n\ntype TooltipProps = {\n tooltip: SunboardTooltip;\n tooltipKey: string;\n requested: boolean;\n onTargetMissing: () => void;\n};\n\n/**\n * Renders at most one tooltip: the first visible candidate whose target\n * resolves. A candidate whose selector never appears is skipped for the rest\n * of the session (without recording a dismissal — the user rejected nothing),\n * so one stale selector can't block the tooltips behind it.\n */\nexport function SunboardTooltips() {\n const { visibleTooltips } = useSunboard();\n const [skippedKeys, setSkippedKeys] = useState<Set<string>>(() => new Set());\n\n const active =\n visibleTooltips.find(({ key }) => !skippedKeys.has(key)) ?? null;\n\n if (!active) {\n return null;\n }\n\n return (\n <Tooltip\n key={active.key}\n onTargetMissing={() =>\n setSkippedKeys((current) => new Set(current).add(active.key))\n }\n requested={active.requested}\n tooltip={active.tooltip}\n tooltipKey={active.key}\n />\n );\n}\n\nfunction Tooltip({\n tooltip,\n tooltipKey,\n requested,\n onTargetMissing\n}: TooltipProps) {\n const { dismissTooltip, markTooltipSeen, startTour } = useSunboard();\n const arrowRef = useRef<SVGSVGElement | null>(null);\n const title = useInterpolated(tooltip.title);\n const body = useInterpolated(tooltip.body);\n const actionLabel = useInterpolated(tooltip.actionLabel) ?? \"Show me how\";\n const target = useTargetElement(tooltip.target, true);\n const { context, refs, floatingStyles, update } = useFloating({\n placement: tooltip.placement,\n middleware: [\n offset(12),\n flip(),\n shift({ padding: 12 }),\n arrow({ element: arrowRef })\n ],\n whileElementsMounted: autoUpdate\n });\n\n useEffect(() => {\n if (!target.timedOut) {\n return;\n }\n\n onTargetMissing();\n // `onTargetMissing` intentionally excluded: it is an inline closure whose\n // identity churns per render, and the skip must fire exactly once.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [target.timedOut]);\n\n useEffect(() => {\n if (!target.element) {\n return;\n }\n\n refs.setReference(target.element);\n void update();\n // The tooltip reached the user's eyes — this is the reach denominator for\n // \"did the nudge move the goal\".\n markTooltipSeen(tooltipKey);\n }, [markTooltipSeen, refs, target.element, tooltipKey, update]);\n\n useEffect(() => {\n const element = target.element;\n\n if (!element) {\n return;\n }\n\n // Clicking the target means the user did the thing the tooltip pointed\n // at: the nudge worked, retire it. Deferred (same as tour advancement)\n // so the app's own click handler runs before the dismissal re-renders.\n const dismissOnTargetClick = () => {\n window.setTimeout(() => {\n void dismissTooltip(tooltipKey, { via: \"target\" });\n }, 0);\n };\n\n element.addEventListener(\"click\", dismissOnTargetClick);\n\n return () => {\n element.removeEventListener(\"click\", dismissOnTargetClick);\n };\n }, [dismissTooltip, target.element, tooltipKey]);\n\n if (!target.element) {\n return null;\n }\n\n return (\n <FloatingPortal>\n <section\n aria-label={title}\n className=\"sunboard-tooltip\"\n data-sunboard-tooltip={tooltipKey}\n data-requested={requested}\n ref={refs.setFloating}\n role=\"dialog\"\n style={floatingStyles}\n >\n <FloatingArrow\n className=\"sunboard-tooltip-arrow\"\n context={context}\n fill=\"var(--sunboard-tour-background)\"\n ref={arrowRef}\n stroke=\"var(--sunboard-tour-border)\"\n strokeWidth={1}\n tipRadius={2}\n />\n <button\n aria-label={`Dismiss ${title}`}\n className=\"sunboard-tooltip-close\"\n onClick={() => void dismissTooltip(tooltipKey)}\n type=\"button\"\n >\n x\n </button>\n <h2 className=\"sunboard-tooltip-title\">{title}</h2>\n {body ? <p className=\"sunboard-tooltip-body\">{body}</p> : null}\n {tooltip.action ? (\n <div className=\"sunboard-tooltip-actions\">\n <button\n className=\"sunboard-tooltip-action\"\n onClick={() => {\n // Taking the action counts as engagement AND retires the\n // tooltip; the tour then owns the screen.\n void dismissTooltip(tooltipKey, { via: \"action\" });\n void startTour(tooltip.action!.tour);\n }}\n type=\"button\"\n >\n {actionLabel}\n </button>\n </div>\n ) : null}\n </section>\n </FloatingPortal>\n );\n}\n","import {\r\n arrow,\r\n FloatingArrow,\r\n FloatingPortal,\r\n flip,\r\n offset,\r\n shift,\r\n useFloating\r\n} from \"@floating-ui/react\";\r\nimport { useEffect, useRef, useState } from \"react\";\r\nimport { routeMatches } from \"../adapters/routes\";\r\nimport { useInterpolated } from \"../hooks/useInterpolated\";\r\nimport { useSunboard } from \"../hooks/useSunboard\";\r\nimport { useTargetElement } from \"../hooks/useTargetElement\";\r\n\r\nexport function SunboardTour() {\r\n const { activeTour, advanceTour, dismissTour, route } = useSunboard();\r\n const arrowRef = useRef<SVGSVGElement | null>(null);\r\n const [spotlightRect, setSpotlightRect] = useState<DOMRect | null>(null);\r\n const routeReady = routeMatches(activeTour?.step.route, route ?? \"/\");\r\n const target = useTargetElement(\r\n activeTour?.step.target ?? null,\r\n Boolean(activeTour) && routeReady,\r\n 1_000\r\n );\r\n const { context, refs, floatingStyles, update } = useFloating({\r\n placement: activeTour?.step.placement ?? \"bottom\",\r\n middleware: [\r\n offset(16),\r\n flip(),\r\n shift({ padding: 12 }),\r\n arrow({ element: arrowRef })\r\n ]\r\n });\r\n\r\n useEffect(() => {\r\n if (!activeTour || !routeReady || !target.timedOut) {\r\n return;\r\n }\r\n\r\n // The target is missing and the user didn't reject anything, so close\r\n // programmatically without recording a dismissal signal.\r\n void dismissTour({ silent: true });\r\n }, [activeTour, dismissTour, routeReady, target.timedOut]);\r\n\r\n useEffect(() => {\r\n if (!activeTour || !target.element) {\r\n return;\r\n }\r\n\r\n refs.setReference(target.element);\r\n target.element.scrollIntoView({ block: \"center\", inline: \"center\" });\r\n void update();\r\n }, [activeTour, refs, target.element, update]);\r\n\r\n useEffect(() => {\r\n if (!activeTour || !target.element) {\r\n setSpotlightRect(null);\r\n return;\r\n }\r\n\r\n const updateSpotlight = () => {\r\n setSpotlightRect(target.element?.getBoundingClientRect() ?? null);\r\n };\r\n\r\n updateSpotlight();\r\n window.addEventListener(\"resize\", updateSpotlight);\r\n window.addEventListener(\"scroll\", updateSpotlight, true);\r\n\r\n target.element.setAttribute(\"data-sunboard-tour-target\", \"true\");\r\n\r\n return () => {\r\n window.removeEventListener(\"resize\", updateSpotlight);\r\n window.removeEventListener(\"scroll\", updateSpotlight, true);\r\n target.element?.removeAttribute(\"data-sunboard-tour-target\");\r\n setSpotlightRect(null);\r\n };\r\n }, [activeTour, target.element]);\r\n\r\n useEffect(() => {\r\n if (!activeTour || !target.element) {\r\n return;\r\n }\r\n\r\n const advanceOnTargetClick = () => {\r\n window.setTimeout(() => {\r\n void advanceTour();\r\n }, 0);\r\n };\r\n\r\n target.element.addEventListener(\"click\", advanceOnTargetClick);\r\n\r\n return () => {\r\n target.element?.removeEventListener(\"click\", advanceOnTargetClick);\r\n };\r\n }, [activeTour, advanceTour, target.element]);\r\n\r\n const tourName = useInterpolated(activeTour?.name);\r\n const stepTitle = useInterpolated(activeTour?.step.title);\r\n const stepBody = useInterpolated(activeTour?.step.body);\r\n\r\n if (!activeTour) {\r\n return null;\r\n }\r\n\r\n if (!routeReady || !target.element) {\r\n return null;\r\n }\r\n\r\n const isLastStep = activeTour.stepIndex === activeTour.totalSteps - 1;\r\n\r\n return (\r\n <FloatingPortal>\r\n <SunboardTourScrim rect={spotlightRect} />\r\n <section\r\n aria-label={`${tourName} tour step`}\r\n className=\"sunboard-tour\"\r\n ref={refs.setFloating}\r\n role=\"dialog\"\r\n style={floatingStyles}\r\n >\r\n <FloatingArrow\r\n className=\"sunboard-tour-arrow\"\r\n context={context}\r\n fill=\"var(--sunboard-tour-background)\"\r\n ref={arrowRef}\r\n stroke=\"var(--sunboard-tour-border)\"\r\n strokeWidth={1}\r\n tipRadius={2}\r\n />\r\n <div className=\"sunboard-tour-meta\">\r\n {activeTour.stepIndex + 1} / {activeTour.totalSteps}\r\n </div>\r\n <h2 className=\"sunboard-tour-title\">{stepTitle}</h2>\r\n {stepBody ? (\r\n <p className=\"sunboard-tour-body\">{stepBody}</p>\r\n ) : null}\r\n <div className=\"sunboard-tour-actions\">\r\n <button\r\n className=\"sunboard-tour-secondary\"\r\n onClick={() => void dismissTour()}\r\n type=\"button\"\r\n >\r\n Dismiss\r\n </button>\r\n <button\r\n className=\"sunboard-tour-primary\"\r\n onClick={() => void advanceTour()}\r\n type=\"button\"\r\n >\r\n {isLastStep ? \"Finish\" : \"Next\"}\r\n </button>\r\n </div>\r\n </section>\r\n </FloatingPortal>\r\n );\r\n}\r\n\r\nfunction SunboardTourScrim({ rect }: { rect: DOMRect | null }) {\r\n if (!rect) {\r\n return <div className=\"sunboard-tour-scrim\" />;\r\n }\r\n\r\n const padding = 8;\r\n const radius = 12;\r\n const left = Math.max(rect.left - padding, 0);\r\n const top = Math.max(rect.top - padding, 0);\r\n const width = rect.width + padding * 2;\r\n const height = rect.height + padding * 2;\r\n const maskId = \"sunboard-tour-spotlight-mask\";\r\n\r\n return (\r\n <svg\r\n aria-hidden=\"true\"\r\n className=\"sunboard-tour-scrim sunboard-tour-spotlight\"\r\n focusable=\"false\"\r\n >\r\n <defs>\r\n <mask id={maskId}>\r\n <rect fill=\"white\" height=\"100%\" width=\"100%\" x=\"0\" y=\"0\" />\r\n <rect\r\n fill=\"black\"\r\n height={height}\r\n rx={radius}\r\n ry={radius}\r\n width={width}\r\n x={left}\r\n y={top}\r\n />\r\n </mask>\r\n </defs>\r\n <rect\r\n fill=\"var(--sunboard-overlay-color)\"\r\n height=\"100%\"\r\n mask={`url(#${maskId})`}\r\n width=\"100%\"\r\n x=\"0\"\r\n y=\"0\"\r\n />\r\n </svg>\r\n );\r\n}\r\n","import { useEffect, useState } from \"react\";\r\nimport { useChecklist } from \"../hooks/useChecklist\";\r\nimport { useSurvey } from \"../hooks/useSurvey\";\r\nimport { ListIcon, XIcon } from \"../icons\";\r\nimport { ChecklistStep } from \"./ChecklistStep\";\r\nimport { SunboardHotspots } from \"./SunboardHotspots\";\r\nimport { SunboardTooltips } from \"./SunboardTooltips\";\r\nimport { SunboardTour } from \"./SunboardTour\";\r\n\r\nexport function SunboardChecklist() {\r\n const [open, setOpen] = useState(true);\r\n const {\r\n visible,\r\n title,\r\n subtitle,\r\n steps,\r\n completedCount,\r\n totalSteps,\r\n remainingCount,\r\n progress,\r\n isCompleted,\r\n startStep,\r\n completion,\r\n dismissCompletion\r\n } = useChecklist();\r\n\r\n // Auto-dismiss the completion screen after the spec-author-specified delay.\r\n // Lives in the widget rather than the hook so custom checklists can choose\r\n // their own timing UX (e.g. dismiss on tab change) without inheriting ours.\r\n useEffect(() => {\r\n if (!completion?.dismissAfterMs) return;\r\n const handle = window.setTimeout(dismissCompletion, completion.dismissAfterMs);\r\n return () => window.clearTimeout(handle);\r\n }, [completion, dismissCompletion]);\r\n\r\n if (!visible || !title) {\r\n return null;\r\n }\r\n\r\n if (completion) {\r\n return (\r\n <aside\r\n className=\"sunboard-root sunboard-root-completion\"\r\n aria-label={`${title} complete`}\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n >\r\n <button\r\n className=\"sunboard-close sunboard-completion-close\"\r\n type=\"button\"\r\n onClick={dismissCompletion}\r\n aria-label=\"Dismiss completion message\"\r\n >\r\n <XIcon />\r\n </button>\r\n <div className=\"sunboard-completion\">\r\n <div className=\"sunboard-completion-check\" aria-hidden=\"true\">\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"3\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <polyline points=\"5 12 10 17 19 8\" />\r\n </svg>\r\n </div>\r\n {completion.subtitle ? (\r\n <p className=\"sunboard-completion-subtitle\">{completion.subtitle}</p>\r\n ) : null}\r\n <h2 className=\"sunboard-completion-title\">{completion.title}</h2>\r\n {completion.message ? (\r\n <p className=\"sunboard-completion-message\">{completion.message}</p>\r\n ) : null}\r\n </div>\r\n </aside>\r\n );\r\n }\r\n\r\n if (!open) {\r\n return (\r\n <button\r\n className=\"sunboard-launcher\"\r\n type=\"button\"\r\n onClick={() => setOpen(true)}\r\n aria-label={`Open ${title} checklist, ${remainingCount} tasks remaining`}\r\n >\r\n {remainingCount > 0 ? (\r\n <span className=\"sunboard-launcher-badge\">{remainingCount}</span>\r\n ) : null}\r\n <ListIcon />\r\n </button>\r\n );\r\n }\r\n\r\n return (\r\n <aside className=\"sunboard-root\" aria-label={`${title} checklist`}>\r\n <div className=\"sunboard-header\">\r\n <div>\r\n {subtitle ? <p className=\"sunboard-eyebrow\">{subtitle}</p> : null}\r\n <h2 className=\"sunboard-title\">{title}</h2>\r\n </div>\r\n <div className=\"sunboard-header-actions\">\r\n <p\r\n className=\"sunboard-progress-count\"\r\n aria-label=\"Checklist progress\"\r\n >\r\n {completedCount} / {totalSteps}\r\n </p>\r\n <button\r\n className=\"sunboard-close\"\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n aria-label={`Close ${title} checklist`}\r\n >\r\n <XIcon />\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div\r\n className=\"sunboard-progress\"\r\n role=\"progressbar\"\r\n aria-label={`${title} progress`}\r\n aria-valuemin={0}\r\n aria-valuemax={totalSteps}\r\n aria-valuenow={completedCount}\r\n >\r\n <div\r\n className=\"sunboard-progress-fill\"\r\n style={{ width: `${progress}%` }}\r\n />\r\n </div>\r\n\r\n <ol className=\"sunboard-step-list\">\r\n {steps.map((step) => (\r\n <ChecklistStep\r\n completed={isCompleted(step.key)}\r\n key={step.key}\r\n onStart={() => startStep(step)}\r\n step={step}\r\n />\r\n ))}\r\n </ol>\r\n </aside>\r\n );\r\n}\r\n\r\nexport function SunboardSurvey() {\r\n const {\r\n visible,\r\n title,\r\n subtitle,\r\n currentStep,\r\n stepIndex,\r\n totalSteps,\r\n progress,\r\n isFirstStep,\r\n isLastStep,\r\n canAdvance,\r\n canSubmit,\r\n next,\r\n back,\r\n answers,\r\n setAnswer,\r\n toggleAnswer,\r\n submitting,\r\n error\r\n } = useSurvey();\r\n\r\n if (!visible || !title || !currentStep) {\r\n return null;\r\n }\r\n\r\n const showProgress = totalSteps > 1;\r\n\r\n return (\r\n <div className=\"sunboard-survey-overlay\">\r\n <section\r\n className=\"sunboard-survey-card\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-label={`${title} survey`}\r\n >\r\n <div className=\"sunboard-survey-head\">\r\n {subtitle ? <p className=\"sunboard-eyebrow\">{subtitle}</p> : null}\r\n <h2 className=\"sunboard-title\">{title}</h2>\r\n </div>\r\n\r\n {showProgress ? (\r\n <div className=\"sunboard-survey-progress-row\">\r\n <p className=\"sunboard-survey-stepcount\">\r\n Step {stepIndex + 1} of {totalSteps}\r\n </p>\r\n <div\r\n className=\"sunboard-progress\"\r\n role=\"progressbar\"\r\n aria-label={`${title} progress`}\r\n aria-valuemin={1}\r\n aria-valuemax={totalSteps}\r\n aria-valuenow={stepIndex + 1}\r\n >\r\n <div\r\n className=\"sunboard-progress-fill\"\r\n style={{ width: `${progress}%` }}\r\n />\r\n </div>\r\n </div>\r\n ) : null}\r\n\r\n <form\r\n className=\"sunboard-survey-form\"\r\n onSubmit={(event) => {\r\n event.preventDefault();\r\n next();\r\n }}\r\n >\r\n {currentStep.title ? (\r\n <p className=\"sunboard-survey-step-title\">{currentStep.title}</p>\r\n ) : null}\r\n\r\n <ol className=\"sunboard-survey-questions\">\r\n {currentStep.questions.map((question) => (\r\n <li className=\"sunboard-survey-question\" key={question.key}>\r\n <p className=\"sunboard-survey-prompt\">\r\n {question.prompt}\r\n {question.required ? (\r\n <span aria-hidden=\"true\" className=\"sunboard-survey-required\">\r\n {\" *\"}\r\n </span>\r\n ) : null}\r\n </p>\r\n\r\n {question.type === \"text\" ? (\r\n <input\r\n className=\"sunboard-survey-input\"\r\n type=\"text\"\r\n value={\r\n typeof answers[question.key] === \"string\"\r\n ? (answers[question.key] as string)\r\n : \"\"\r\n }\r\n required={question.required}\r\n aria-label={question.prompt}\r\n onChange={(event) =>\r\n setAnswer(question.key, event.target.value)\r\n }\r\n />\r\n ) : (\r\n <div\r\n className=\"sunboard-survey-options\"\r\n role=\"group\"\r\n aria-label={question.prompt}\r\n >\r\n {question.options.map((option) => {\r\n const selected =\r\n question.type === \"multi-select\"\r\n ? Array.isArray(answers[question.key]) &&\r\n (answers[question.key] as string[]).includes(\r\n option.value\r\n )\r\n : answers[question.key] === option.value;\r\n\r\n return (\r\n <button\r\n className=\"sunboard-survey-option\"\r\n type=\"button\"\r\n key={option.value}\r\n aria-pressed={selected}\r\n data-selected={selected ? \"true\" : \"false\"}\r\n onClick={() =>\r\n question.type === \"multi-select\"\r\n ? toggleAnswer(question.key, option.value)\r\n : setAnswer(question.key, option.value)\r\n }\r\n >\r\n {option.label}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </li>\r\n ))}\r\n </ol>\r\n\r\n {error ? (\r\n <p className=\"sunboard-survey-error\" role=\"alert\">\r\n {error}\r\n </p>\r\n ) : null}\r\n\r\n <div className=\"sunboard-survey-nav\">\r\n {!isFirstStep ? (\r\n <button\r\n className=\"sunboard-survey-back\"\r\n type=\"button\"\r\n onClick={back}\r\n >\r\n Back\r\n </button>\r\n ) : (\r\n <span />\r\n )}\r\n <button\r\n className=\"sunboard-survey-submit\"\r\n type=\"submit\"\r\n disabled={isLastStep ? !canSubmit : !canAdvance}\r\n >\r\n {isLastStep ? (submitting ? \"Submitting…\" : \"Finish\") : \"Continue\"}\r\n </button>\r\n </div>\r\n </form>\r\n </section>\r\n </div>\r\n );\r\n}\r\n\r\nexport const Sunboard = Object.assign(SunboardChecklist, {\r\n Checklist: SunboardChecklist,\r\n Survey: SunboardSurvey,\r\n Hotspots: SunboardHotspots,\r\n Tooltips: SunboardTooltips,\r\n Tour: SunboardTour\r\n});\r\n","import { useCallback, useMemo } from \"react\";\nimport type { SunboardPlacement } from \"@sunboard/core\";\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\nimport { useSunboard } from \"./useSunboard\";\n\nexport type TooltipView = {\n key: string;\n title: string;\n body?: string;\n /** CSS selector of the element the tooltip points at. */\n target: string;\n placement: SunboardPlacement;\n /** True when a tour action is attached; `engage` starts it. */\n hasAction: boolean;\n actionLabel?: string;\n /** True when the user asked for this tooltip via a checklist step. */\n requested: boolean;\n};\n\nexport type UseTooltipsResult = {\n /**\n * Tooltips that qualify for the current route (undismissed, or explicitly\n * requested). Render ONE at a time — the first whose target you can anchor.\n */\n tooltips: TooltipView[];\n /** Report that a tooltip is actually on screen (deduped per session). */\n markSeen: (key: string) => void;\n /** Close without acting — the tooltip never shows again. */\n dismiss: (key: string) => void;\n /** Take the tooltip's action: starts its tour and retires the tooltip. */\n engage: (key: string) => void;\n /**\n * Report that the user clicked the tooltip's target element: the nudge\n * worked, so this retires the tooltip and records the engagement.\n */\n markTargetClicked: (key: string) => void;\n};\n\n/**\n * Headless counterpart of `<SunboardTooltips>`: the same visibility, seen,\n * dismissal, and engagement semantics with rendering left to the host.\n */\nexport function useTooltips(): UseTooltipsResult {\n const {\n user,\n visibleTooltips,\n markTooltipSeen,\n dismissTooltip,\n startTour\n } = useSunboard();\n\n const tooltips = useMemo<TooltipView[]>(\n () =>\n visibleTooltips.map(({ key, tooltip, requested }) => ({\n key,\n title: interpolateTokens(tooltip.title, user ?? null),\n body: tooltip.body\n ? interpolateTokens(tooltip.body, user ?? null)\n : undefined,\n target: tooltip.target,\n placement: tooltip.placement,\n hasAction: Boolean(tooltip.action),\n actionLabel: tooltip.actionLabel\n ? interpolateTokens(tooltip.actionLabel, user ?? null)\n : undefined,\n requested\n })),\n [user, visibleTooltips]\n );\n\n const engage = useCallback(\n (key: string) => {\n const entry = visibleTooltips.find((candidate) => candidate.key === key);\n\n void dismissTooltip(key, { via: \"action\" });\n\n if (entry?.tooltip.action) {\n void startTour(entry.tooltip.action.tour);\n }\n },\n [dismissTooltip, startTour, visibleTooltips]\n );\n\n return {\n tooltips,\n markSeen: markTooltipSeen,\n dismiss: (key) => {\n void dismissTooltip(key);\n },\n engage,\n markTargetClicked: (key) => {\n void dismissTooltip(key, { via: \"target\" });\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["identities: Record<string, string>","best: PosthogCandidate | undefined","found: PosthogCandidate[]","SunboardRuntimeClient","via: TooltipDismissVia","flat: FlatUser","current: unknown","interpolateOptional","next"],"sources":["../src/adapters/navigation.ts","../src/lib/detect-identities.ts","../src/hooks/sunboard-context.ts","../src/version.ts","../src/components/SunboardProvider.tsx","../src/lib/interpolate-tokens.ts","../src/hooks/useSunboard.ts","../src/hooks/useChecklist.ts","../src/hooks/useSurvey.ts","../src/icons/index.tsx","../src/components/ChecklistStep.tsx","../src/hooks/useInterpolated.ts","../src/hooks/useTargetElement.ts","../src/components/SunboardHotspots.tsx","../src/components/SunboardTooltips.tsx","../src/components/SunboardTour.tsx","../src/components/Sunboard.tsx","../src/hooks/useTooltips.ts"],"sourcesContent":["export type NavigateCallback = (url: string) => void | Promise<void>;\n\nexport async function navigateWithFallback(\n url: string,\n navigate?: NavigateCallback\n): Promise<void> {\n if (navigate) {\n await navigate(url);\n return;\n }\n\n if (typeof window !== \"undefined\") {\n window.location.assign(url);\n }\n}\n","/**\n * Best-effort probe of analytics providers on the host page, returning the id\n * each one files the current visitor under (e.g. PostHog's distinct_id). These\n * ride the bootstrap request so the server can join Sunboard's funnel data to\n * the customer's analytics; they are inert until the project connects the\n * provider in Sunboard.\n *\n * Everything here is defensive by construction: a broken or half-loaded\n * analytics snippet on the customer's page must never break onboarding, so\n * every probe is wrapped and failures return nothing.\n */\n\n/**\n * The `identities` prop accepted by SunboardProvider:\n * - omitted: providers are auto-detected on each bootstrap\n * - object: explicit ids, merged over (and winning against) detected ones\n * - `false`: detection disabled entirely, nothing is sent\n */\nexport type IdentitiesProp = Record<string, string> | false;\n\nexport function resolveIdentities(\n prop: IdentitiesProp | undefined\n): Record<string, string> | undefined {\n if (prop === false) {\n return undefined;\n }\n\n const detected = detectAnalyticsIdentities();\n const merged = { ...detected, ...prop };\n\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction detectAnalyticsIdentities(): Record<string, string> {\n const identities: Record<string, string> = {};\n\n if (typeof window === \"undefined\") {\n return identities;\n }\n\n const posthogId = detectPosthogId();\n if (posthogId) {\n identities.posthog = posthogId;\n }\n\n // Future providers (amplitude, mixpanel) add a probe here and nothing else;\n // the wire format and storage are already keyed by provider.\n\n return identities;\n}\n\nfunction detectPosthogId(): string | undefined {\n // A live instance is the most direct read, but only snippet installs assign\n // `window.posthog`; the posthog-js npm module (the common React setup) never\n // touches the global.\n const fromInstance = probe(() => {\n const posthog = (window as PosthogWindow).posthog;\n return typeof posthog?.get_distinct_id === \"function\"\n ? posthog.get_distinct_id()\n : undefined;\n });\n if (fromInstance) {\n return fromInstance;\n }\n\n // Fall back to the persistence posthog-js writes on init, keyed\n // `ph_<projectApiKey>_posthog` with the distinct_id inside: localStorage\n // for the default `localStorage`/`localStorage+cookie` modes, the cookie\n // for `cookie` mode. Several entries can coexist on one origin (every\n // PostHog project ever run there leaves one behind, localhost especially)\n // and each carries a different distinct_id, so pick the entry with the\n // most recent session activity: only the currently running project keeps\n // bumping its `$sesid` timestamp.\n const candidates = [\n ...posthogCandidatesFromLocalStorage(),\n ...posthogCandidatesFromCookie()\n ];\n\n let best: PosthogCandidate | undefined;\n for (const candidate of candidates) {\n if (!best || candidate.lastActivity > best.lastActivity) {\n best = candidate;\n }\n }\n return best?.distinctId;\n}\n\nconst POSTHOG_PERSISTENCE_KEY = /^ph_.+_posthog$/;\n\ntype PosthogCandidate = {\n distinctId: string;\n /** Last session activity (`$sesid[0]`); 0 when the entry has none. */\n lastActivity: number;\n};\n\nfunction posthogCandidatesFromLocalStorage(): PosthogCandidate[] {\n const found: PosthogCandidate[] = [];\n try {\n const storage = window.localStorage;\n for (let index = 0; index < storage.length; index += 1) {\n const key = storage.key(index);\n if (!key || !POSTHOG_PERSISTENCE_KEY.test(key)) {\n continue;\n }\n const candidate = parsePosthogPersistence(() => storage.getItem(key));\n if (candidate) {\n found.push(candidate);\n }\n }\n } catch {\n // localStorage can be blocked outright (privacy modes); nothing to read.\n }\n return found;\n}\n\nfunction posthogCandidatesFromCookie(): PosthogCandidate[] {\n const found: PosthogCandidate[] = [];\n try {\n for (const entry of document.cookie.split(\";\")) {\n const separator = entry.indexOf(\"=\");\n if (separator === -1) {\n continue;\n }\n const name = entry.slice(0, separator).trim();\n if (!POSTHOG_PERSISTENCE_KEY.test(name)) {\n continue;\n }\n const candidate = parsePosthogPersistence(() =>\n decodeURIComponent(entry.slice(separator + 1))\n );\n if (candidate) {\n found.push(candidate);\n }\n }\n } catch {\n // Cookie access can throw in exotic embeds; nothing to read.\n }\n return found;\n}\n\n/** One persistence entry -> candidate; malformed entries parse to nothing. */\nfunction parsePosthogPersistence(\n read: () => string | null\n): PosthogCandidate | undefined {\n try {\n const raw = read();\n if (!raw) {\n return undefined;\n }\n const parsed = JSON.parse(raw) as {\n distinct_id?: unknown;\n $sesid?: unknown;\n };\n if (\n typeof parsed.distinct_id !== \"string\" ||\n parsed.distinct_id.length === 0\n ) {\n return undefined;\n }\n const sesid = parsed.$sesid;\n return {\n distinctId: parsed.distinct_id,\n lastActivity:\n Array.isArray(sesid) && typeof sesid[0] === \"number\" ? sesid[0] : 0\n };\n } catch {\n return undefined;\n }\n}\n\n/** Run a probe, keeping only non-empty string results and swallowing throws. */\nfunction probe(read: () => unknown): string | undefined {\n try {\n const value = read();\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\ntype PosthogWindow = Window & {\n posthog?: {\n get_distinct_id?: () => unknown;\n };\n};\n","import { createContext } from \"react\";\nimport type {\n NormalizedSunboardSpec,\n SunboardHotspot,\n SunboardStep,\n SunboardTooltip,\n SunboardTourStep,\n TrackEventName\n} from \"@sunboard/core\";\nimport type { TooltipDismissVia } from \"@sunboard/core/runtime-api\";\n\nexport type SunboardUser = {\n id: string;\n email?: string | null;\n group?: string | null;\n properties?: Record<string, unknown>;\n};\n\nexport type TrackProperties = Record<string, unknown>;\n\nexport type TrackResult =\n | { ok: true }\n | { ok: false; reason: \"not-ready\" | \"missing-user\" | \"network-error\" };\n\nexport type SurveyResult =\n | { ok: true }\n | {\n ok: false;\n reason: \"not-ready\" | \"missing-user\" | \"not-survey\" | \"network-error\";\n };\n\nexport type TourResult =\n | { ok: true }\n | { ok: false; reason: \"not-ready\" | \"missing-user\" | \"missing-tour\" };\n\nexport type StepResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"not-ready\"\n | \"missing-user\"\n | \"missing-step\"\n | \"not-manual\"\n | \"network-error\";\n };\n\nexport type TooltipResult =\n | { ok: true }\n | { ok: false; reason: \"not-ready\" | \"missing-user\" | \"missing-tooltip\" };\n\nexport type VisibleSunboardTooltip = {\n key: string;\n tooltip: SunboardTooltip;\n /**\n * True when the user asked for this tooltip (a checklist step's\n * `openTooltip` action), false when it is showing proactively. A requested\n * tooltip shows even if it was dismissed before — the user explicitly asked\n * to be shown where the action happens.\n */\n requested: boolean;\n};\n\nexport type ActiveSunboardTour = {\n key: string;\n name: string;\n step: SunboardTourStep;\n stepIndex: number;\n totalSteps: number;\n};\n\nexport type SunboardContextValue = {\n spec?: NormalizedSunboardSpec | null;\n user?: SunboardUser | null;\n loading: boolean;\n route?: string;\n completedStepKeys: ReadonlySet<string>;\n completedTourKeys: ReadonlySet<string>;\n completedCount: number;\n totalSteps: number;\n activeTour: ActiveSunboardTour | null;\n track: (\n eventName: TrackEventName,\n properties?: TrackProperties\n ) => Promise<TrackResult>;\n /**\n * Submit survey answers for the active survey experience. On success the\n * runtime re-bootstraps so the freshly collected attributes route the user\n * into the next experience in-session.\n */\n submitSurvey: (answers: Record<string, unknown>) => Promise<SurveyResult>;\n startTour: (tourKey: string) => Promise<TourResult>;\n /**\n * Close the active tour. User-initiated dismissals are reported to the\n * runtime API as a negative signal (\"the user bailed at this step\");\n * pass `silent: true` for programmatic closes (e.g. a missing tour\n * target) that say nothing about the user's intent.\n */\n dismissTour: (options?: { silent?: boolean }) => Promise<TourResult>;\n advanceTour: () => Promise<TourResult>;\n navigateToStep: (step: SunboardStep) => Promise<void>;\n startChecklistStep: (\n step: SunboardStep\n ) => Promise<TourResult | TooltipResult | void>;\n /**\n * Complete a checklist step because the user checked it off. Only steps the\n * spec allows (`manual` not set to false) accept it; the completion is\n * recorded with a \"manual\" source, distinct from event/tour completions.\n */\n completeChecklistStep: (stepKey: string) => Promise<StepResult>;\n visibleHotspots: Array<{ key: string; hotspot: SunboardHotspot }>;\n dismissedTooltipKeys: ReadonlySet<string>;\n /**\n * Tooltips that qualify for the current route, requested-first. Renderers\n * show ONE at a time: the first entry whose target resolves.\n */\n visibleTooltips: VisibleSunboardTooltip[];\n /**\n * Open a tooltip on demand (the checklist's openTooltip action). Navigates\n * are the caller's job; this just marks the tooltip requested so it renders\n * once its route matches and its target is present, dismissal or not.\n */\n openTooltip: (tooltipKey: string) => Promise<TooltipResult>;\n /**\n * Never show this tooltip again. `via: \"close\"` is the negative signal (the\n * default); `via: \"action\"` records that the user took the tooltip's action;\n * `via: \"target\"` records that the user clicked the tooltip's target element.\n */\n dismissTooltip: (\n tooltipKey: string,\n options?: { via?: TooltipDismissVia }\n ) => Promise<TooltipResult>;\n /** Report that a tooltip actually reached the user's eyes (deduped per session). */\n markTooltipSeen: (tooltipKey: string) => void;\n};\n\nexport const SunboardContext = createContext<SunboardContextValue | null>(null);\n","export const SDK_VERSION = \"0.0.0\";\n","import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type Dispatch,\n type ReactNode,\n type SetStateAction\n} from \"react\";\nimport type {\n NormalizedSunboardSpec,\n SunboardStep,\n TrackEventName\n} from \"@sunboard/core\";\nimport {\n DEFAULT_RUNTIME_API_URL,\n SunboardRuntimeClient,\n type RuntimeExperience,\n type RuntimeProgress,\n type RuntimeSurvey,\n type TooltipDismissVia\n} from \"@sunboard/core/runtime-api\";\nimport {\n navigateWithFallback,\n type NavigateCallback\n} from \"../adapters/navigation\";\nimport { routeMatches } from \"../adapters/routes\";\nimport {\n resolveIdentities,\n type IdentitiesProp\n} from \"../lib/detect-identities\";\nimport {\n SunboardContext,\n type ActiveSunboardTour,\n type SunboardContextValue,\n type SunboardUser,\n type TrackProperties\n} from \"../hooks/sunboard-context\";\nimport { SDK_VERSION } from \"../version\";\n\nexport type SunboardProviderProps = {\n spec?: NormalizedSunboardSpec | null;\n user?: SunboardUser | null;\n loading?: boolean;\n route?: string;\n navigate?: NavigateCallback;\n /**\n * Override the runtime API host. Dev-only — leave unset in production so the\n * SDK targets the hosted Sunboard runtime API. Defaults to\n * `DEFAULT_RUNTIME_API_URL`.\n */\n apiUrl?: string;\n publishableKey?: string;\n /**\n * Analytics-provider ids for joining Sunboard's funnel data to your product\n * analytics. Omit it and installed providers (PostHog) are detected\n * automatically, on each bootstrap and for a few seconds after, so a\n * provider that initializes late is still captured. Pass explicit ids to\n * override detection; pass `false` to disable capture entirely. Captured ids\n * are inert until the provider is connected to the project in Sunboard.\n */\n identities?: IdentitiesProp;\n onTrack?: (\n eventName: TrackEventName,\n properties?: TrackProperties\n ) => void | Promise<void>;\n children: ReactNode;\n};\n\ntype ActiveTourState = {\n key: string;\n stepIndex: number;\n};\n\ntype BootstrapState = {\n experience: RuntimeExperience | null;\n survey: RuntimeSurvey | null;\n attributes: Record<string, unknown> | null;\n loading: boolean;\n};\n\nexport function SunboardProvider({\n spec: specProp,\n user,\n loading: loadingProp,\n route,\n navigate,\n apiUrl,\n publishableKey,\n identities: identitiesProp,\n onTrack,\n children\n}: SunboardProviderProps) {\n const currentRoute = route ?? getBrowserRoute();\n const currentOrigin = getBrowserOrigin();\n const client = useMemo(() => {\n if (!publishableKey) {\n return null;\n }\n\n // `apiUrl` is a dev-only override; production apps only supply a key and\n // talk to the hosted runtime API by default.\n return new SunboardRuntimeClient({\n apiKey: publishableKey,\n apiUrl: apiUrl || DEFAULT_RUNTIME_API_URL,\n sdkVersion: SDK_VERSION\n });\n }, [apiUrl, publishableKey]);\n // Bump to force a bootstrap refetch in-session (e.g. after a survey submit,\n // so freshly collected attributes route the user without a navigation).\n const [bootstrapNonce, setBootstrapNonce] = useState(0);\n const refreshBootstrap = useCallback(() => {\n setBootstrapNonce((nonce) => nonce + 1);\n }, []);\n\n const bootstrap = useSunboardBootstrap(client, {\n enabled: !specProp,\n identities: identitiesProp,\n nonce: bootstrapNonce,\n origin: currentOrigin,\n route: currentRoute,\n user\n });\n\n const activeExperience = bootstrap.experience;\n // A survey pre-empts experiences (the server returns no experiences while one\n // is owed). Render the survey's spec when present, otherwise the experience's.\n const activeSurvey = bootstrap.survey;\n const hostedSpec = activeSurvey?.spec ?? activeExperience?.spec ?? null;\n const resolvedSpec = specProp ?? hostedSpec;\n const resolvedLoading = loadingProp ?? bootstrap.loading;\n const hostedMode = client !== null && !specProp;\n\n // The user view used for token interpolation/display merges the server-owned\n // attributes (the `onboarding.*` namespace returned by bootstrap) into the\n // host user, so `{{user.onboarding.*}}` resolves. Network calls keep using\n // the original `user` — owned attributes must never be echoed back as host\n // properties (which bootstrap overwrites).\n const ownedAttributes = bootstrap.attributes;\n const effectiveUser = useMemo<SunboardUser | null | undefined>(() => {\n if (!user || !ownedAttributes || Object.keys(ownedAttributes).length === 0) {\n return user;\n }\n\n return {\n ...user,\n properties: { ...(user.properties ?? {}), ...ownedAttributes }\n };\n }, [user, ownedAttributes]);\n\n const [completedStepKeys, setCompletedStepKeys] = useState<Set<string>>(\n () => new Set()\n );\n const [completedTourKeys, setCompletedTourKeys] = useState<Set<string>>(\n () => new Set()\n );\n const [dismissedTooltipKeys, setDismissedTooltipKeys] = useState<Set<string>>(\n () => new Set()\n );\n const [activeTourState, setActiveTourState] = useState<ActiveTourState | null>(\n null\n );\n // A tooltip the user explicitly asked for (checklist openTooltip action).\n // Survives navigation so the tooltip can open once the destination renders.\n const [requestedTooltipKey, setRequestedTooltipKey] = useState<string | null>(\n null\n );\n // Session-level dedupe for tooltip \"seen\" reporting.\n const seenTooltipKeys = useRef<Set<string>>(new Set());\n const lastNavigatedTourStep = useRef<string | null>(null);\n const storageKey =\n !hostedMode && resolvedSpec && user\n ? completedToursStorageKey(resolvedSpec.key, user.id)\n : null;\n const tooltipStorageKey =\n !hostedMode && resolvedSpec && user\n ? dismissedTooltipsStorageKey(resolvedSpec.key, user.id)\n : null;\n const experienceKey =\n activeSurvey?.key ?? activeExperience?.key ?? resolvedSpec?.key ?? null;\n const versionId =\n activeSurvey?.version.id ?? activeExperience?.version.id ?? null;\n\n useEffect(() => {\n setCompletedStepKeys(new Set());\n setActiveTourState(null);\n setRequestedTooltipKey(null);\n seenTooltipKeys.current = new Set();\n lastNavigatedTourStep.current = null;\n\n setCompletedTourKeys(\n storageKey ? readStoredKeys(storageKey) : new Set()\n );\n setDismissedTooltipKeys(\n tooltipStorageKey ? readStoredKeys(tooltipStorageKey) : new Set()\n );\n }, [resolvedSpec?.key, storageKey, tooltipStorageKey, user?.id]);\n\n useEffect(() => {\n if (!hostedMode || !activeExperience) {\n return;\n }\n\n applyProgress(\n activeExperience.progress,\n setCompletedStepKeys,\n setCompletedTourKeys,\n setDismissedTooltipKeys\n );\n }, [activeExperience, hostedMode]);\n\n const effectiveCompletedStepKeys = useMemo(() => {\n if (!hostedMode || !activeExperience) {\n return completedStepKeys;\n }\n\n return mergeKeys(\n completedStepKeys,\n activeExperience.progress.completedStepKeys\n );\n }, [activeExperience, completedStepKeys, hostedMode]);\n\n const effectiveCompletedTourKeys = useMemo(() => {\n if (!hostedMode || !activeExperience) {\n return completedTourKeys;\n }\n\n return mergeKeys(\n completedTourKeys,\n activeExperience.progress.completedTourKeys\n );\n }, [activeExperience, completedTourKeys, hostedMode]);\n\n const effectiveDismissedTooltipKeys = useMemo(() => {\n if (!hostedMode || !activeExperience) {\n return dismissedTooltipKeys;\n }\n\n return mergeKeys(\n dismissedTooltipKeys,\n activeExperience.progress.dismissedTooltipKeys\n );\n }, [activeExperience, dismissedTooltipKeys, hostedMode]);\n\n useEffect(() => {\n if (!resolvedSpec) {\n return;\n }\n\n const tourCompletedStepKeys = (resolvedSpec.checklist?.steps ?? [])\n .filter(\n (step) =>\n step.completeWhen?.tour &&\n effectiveCompletedTourKeys.has(step.completeWhen.tour)\n )\n .map((step) => step.key);\n\n if (tourCompletedStepKeys.length === 0) {\n return;\n }\n\n setCompletedStepKeys((current) => mergeKeys(current, tourCompletedStepKeys));\n }, [effectiveCompletedTourKeys, resolvedSpec]);\n\n const activeTour = useMemo<ActiveSunboardTour | null>(() => {\n if (!resolvedSpec || !activeTourState) {\n return null;\n }\n\n const tour = resolvedSpec.tours[activeTourState.key];\n const step = tour?.steps[activeTourState.stepIndex];\n\n if (!tour || !step) {\n return null;\n }\n\n return {\n key: activeTourState.key,\n name: tour.name,\n step,\n stepIndex: activeTourState.stepIndex,\n totalSteps: tour.steps.length\n };\n }, [activeTourState, resolvedSpec]);\n\n useEffect(() => {\n if (!activeTour?.step.route || routeMatches(activeTour.step.route, currentRoute)) {\n return;\n }\n\n const navigationKey = `${activeTour.key}:${activeTour.step.key}:${activeTour.step.route}`;\n\n if (lastNavigatedTourStep.current === navigationKey) {\n return;\n }\n\n lastNavigatedTourStep.current = navigationKey;\n void navigateWithFallback(activeTour.step.route, navigate);\n }, [activeTour, currentRoute, navigate]);\n\n // Record per-step tour progress. Fires once per (tour, step) transition;\n // the dep array tracks step.key + tour key so re-renders don't spam the\n // endpoint and re-entries refresh `lastSeenAt` server-side.\n useEffect(() => {\n if (\n !hostedMode ||\n !client ||\n !activeTour ||\n !user ||\n !experienceKey\n ) {\n return;\n }\n\n void client\n .recordTourStep({\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n stepKey: activeTour.step.key,\n tourKey: activeTour.key,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined\n })\n .catch(() => {\n // Best-effort tracking — never break the host app on a 4xx/5xx.\n });\n // Intentionally narrow deps to the tour/step identity. We do not refire\n // when `currentRoute` changes mid-step, because the route adapter scrolls\n // the same popover into a new viewport, not a new logical step.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [activeTour?.key, activeTour?.step.key, client, currentOrigin, experienceKey, hostedMode, user?.id]);\n\n const totalSteps = resolvedSpec?.checklist?.steps.length ?? 0;\n const completedCount =\n resolvedSpec?.checklist?.steps.filter((step) =>\n effectiveCompletedStepKeys.has(step.key)\n ).length ?? 0;\n\n const completeTour = useCallback(\n (tourKey: string) => {\n setCompletedTourKeys((current) => {\n const next = new Set(current);\n next.add(tourKey);\n\n if (storageKey) {\n writeStoredKeys(storageKey, next);\n }\n\n return next;\n });\n },\n [storageKey]\n );\n\n const track = useCallback<SunboardContextValue[\"track\"]>(\n async (eventName, properties) => {\n if (!user) {\n return { ok: false, reason: \"missing-user\" };\n }\n\n // In hosted mode every track is durable history: with no resolved spec\n // (bootstrap in flight, or the user matches nothing) the event is still\n // sent, unscoped, so a checklist that resolves later can complete from\n // it. Only spec-prop mode has nowhere to send an event before the spec\n // exists.\n const ready = !resolvedLoading && !!resolvedSpec;\n\n if (!ready && !(hostedMode && client)) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n const matchingStepKeys = ready\n ? (resolvedSpec.checklist?.steps ?? [])\n .filter((step) => step.completeWhen?.event === eventName)\n .map((step) => step.key)\n : [];\n\n if (matchingStepKeys.length > 0) {\n // `mergeKeys` returns `current` unchanged when every key is already\n // present, so re-firing an already-completed step's event is a no-op\n // and does not churn state/recreate the context value. This is what\n // keeps a consumer that calls `track()` from an effect depending on the\n // whole context value from looping render→track→render.\n setCompletedStepKeys((current) => mergeKeys(current, matchingStepKeys));\n }\n\n let networkOk = true;\n\n if (hostedMode && client) {\n // Scope to the resolved experience so the event row is attributed to\n // it. Unscoped when nothing is resolved or when a survey is on screen\n // (surveys never own product events): the server records the event\n // unattached and fans step completions out across every experience\n // the user resolves to.\n const scopedKey =\n ready && resolvedSpec.type !== \"survey\" ? experienceKey : null;\n\n try {\n const response = await client.track({\n eventName,\n origin: currentOrigin,\n properties,\n route: currentRoute,\n user: toRuntimeUser(user),\n ...(scopedKey\n ? { experienceKey: scopedKey, versionId: versionId ?? undefined }\n : {})\n });\n // A scoped track always returns progress; an unscoped one returns\n // null (it may advance several experiences at once).\n if (response.progress) {\n applyProgress(\n response.progress,\n setCompletedStepKeys,\n setCompletedTourKeys,\n setDismissedTooltipKeys\n );\n }\n } catch {\n networkOk = false;\n }\n }\n\n try {\n await onTrack?.(eventName, properties);\n } catch {\n return { ok: false, reason: \"network-error\" };\n }\n\n if (!networkOk) {\n return { ok: false, reason: \"network-error\" };\n }\n\n return { ok: true };\n },\n [\n client,\n currentRoute,\n currentOrigin,\n experienceKey,\n hostedMode,\n onTrack,\n resolvedLoading,\n resolvedSpec,\n user,\n versionId\n ]\n );\n\n const completeChecklistStep = useCallback<\n SunboardContextValue[\"completeChecklistStep\"]\n >(\n async (stepKey) => {\n if (resolvedLoading || !resolvedSpec) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n if (!user) {\n return { ok: false, reason: \"missing-user\" };\n }\n\n const step = (resolvedSpec.checklist?.steps ?? []).find(\n (candidate) => candidate.key === stepKey\n );\n\n if (!step) {\n return { ok: false, reason: \"missing-step\" };\n }\n\n if (step.manual === false) {\n return { ok: false, reason: \"not-manual\" };\n }\n\n // Optimistic, like track(): `mergeKeys` is a no-op when already\n // complete, and the server response below re-applies truth.\n setCompletedStepKeys((current) => mergeKeys(current, [stepKey]));\n\n if (hostedMode && client && experienceKey) {\n try {\n const response = await client.completeStep({\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n stepKey,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined\n });\n applyProgress(\n response.progress,\n setCompletedStepKeys,\n setCompletedTourKeys,\n setDismissedTooltipKeys\n );\n } catch {\n return { ok: false, reason: \"network-error\" };\n }\n }\n\n return { ok: true };\n },\n [\n client,\n currentOrigin,\n currentRoute,\n experienceKey,\n hostedMode,\n resolvedLoading,\n resolvedSpec,\n user,\n versionId\n ]\n );\n\n const submitSurvey = useCallback<SunboardContextValue[\"submitSurvey\"]>(\n async (answers) => {\n if (resolvedLoading || !resolvedSpec) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n if (!user) {\n return { ok: false, reason: \"missing-user\" };\n }\n\n if (resolvedSpec.type !== \"survey\" || !resolvedSpec.survey) {\n return { ok: false, reason: \"not-survey\" };\n }\n\n // Surveys are server-backed: a survey can only be answered when the\n // runtime resolved it for this user.\n if (!hostedMode || !client || !experienceKey) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n try {\n await client.submitSurvey({\n answers,\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined\n });\n } catch {\n return { ok: false, reason: \"network-error\" };\n }\n\n // Re-bootstrap so the freshly collected attributes route the user now.\n refreshBootstrap();\n return { ok: true };\n },\n [\n client,\n currentOrigin,\n currentRoute,\n experienceKey,\n hostedMode,\n refreshBootstrap,\n resolvedLoading,\n resolvedSpec,\n user,\n versionId\n ]\n );\n\n const navigateToStep = useCallback(\n async (step: SunboardStep) => {\n if (!step.route) {\n return;\n }\n\n await navigateWithFallback(step.route, navigate);\n },\n [navigate]\n );\n\n const startTour = useCallback<SunboardContextValue[\"startTour\"]>(\n async (tourKey) => {\n if (resolvedLoading || !resolvedSpec) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n if (!user) {\n return { ok: false, reason: \"missing-user\" };\n }\n\n if (!resolvedSpec.tours[tourKey]) {\n return { ok: false, reason: \"missing-tour\" };\n }\n\n lastNavigatedTourStep.current = null;\n setActiveTourState({ key: tourKey, stepIndex: 0 });\n\n // Fire-and-forget tour-start. The per-step record fires from the\n // useEffect below when activeTour resolves, so step[0] gets recorded\n // independently. We deliberately don't await — popover UX must not\n // block on network.\n if (hostedMode && client && experienceKey && user) {\n void client\n .startTour({\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n tourKey,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined\n })\n .catch(() => {\n // Tracking is best-effort. Network failures shouldn't surface\n // to the host app.\n });\n }\n\n return { ok: true };\n },\n [\n client,\n currentRoute,\n currentOrigin,\n experienceKey,\n hostedMode,\n resolvedLoading,\n resolvedSpec,\n user,\n versionId\n ]\n );\n\n const dismissTour = useCallback<SunboardContextValue[\"dismissTour\"]>(\n async (options) => {\n // Capture what was on screen before clearing state — the dismissal\n // report needs the tour + step the user actually bailed on.\n const dismissed = activeTourState;\n const dismissedStepKey = dismissed\n ? resolvedSpec?.tours[dismissed.key]?.steps[dismissed.stepIndex]?.key\n : undefined;\n\n setActiveTourState(null);\n lastNavigatedTourStep.current = null;\n\n // Best-effort negative-signal tracking, same contract as startTour:\n // never block the UI on network, never surface failures to the host.\n // Silent dismissals (missing target etc.) are not user intent and are\n // deliberately not reported.\n if (\n !options?.silent &&\n hostedMode &&\n client &&\n experienceKey &&\n user &&\n dismissed &&\n dismissedStepKey\n ) {\n void client\n .dismissTour({\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n stepKey: dismissedStepKey,\n tourKey: dismissed.key,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined\n })\n .catch(() => {\n // Tracking is best-effort.\n });\n }\n\n return { ok: true };\n },\n [\n activeTourState,\n client,\n currentOrigin,\n currentRoute,\n experienceKey,\n hostedMode,\n resolvedSpec,\n user,\n versionId\n ]\n );\n\n const finishActiveTour = useCallback(\n async (tourKey: string) => {\n completeTour(tourKey);\n\n if (!hostedMode || !client || !experienceKey || !user) {\n return;\n }\n\n try {\n const response = await client.completeTour({\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n tourKey,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined\n });\n applyProgress(\n response.progress,\n setCompletedStepKeys,\n setCompletedTourKeys,\n setDismissedTooltipKeys\n );\n } catch {\n // Fail soft: keep local optimistic completion, never break the host app.\n }\n },\n [\n client,\n completeTour,\n currentRoute,\n currentOrigin,\n experienceKey,\n hostedMode,\n user,\n versionId\n ]\n );\n\n const advanceTour = useCallback<SunboardContextValue[\"advanceTour\"]>(\n async () => {\n if (resolvedLoading || !resolvedSpec || !activeTourState) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n if (!user) {\n return { ok: false, reason: \"missing-user\" };\n }\n\n const tour = resolvedSpec.tours[activeTourState.key];\n\n if (!tour) {\n return { ok: false, reason: \"missing-tour\" };\n }\n\n lastNavigatedTourStep.current = null;\n\n if (activeTourState.stepIndex >= tour.steps.length - 1) {\n const completedKey = activeTourState.key;\n setActiveTourState(null);\n await finishActiveTour(completedKey);\n return { ok: true };\n }\n\n setActiveTourState({\n key: activeTourState.key,\n stepIndex: activeTourState.stepIndex + 1\n });\n return { ok: true };\n },\n [activeTourState, finishActiveTour, resolvedLoading, resolvedSpec, user]\n );\n\n const openTooltip = useCallback<SunboardContextValue[\"openTooltip\"]>(\n async (tooltipKey) => {\n if (resolvedLoading || !resolvedSpec) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n if (!user) {\n return { ok: false, reason: \"missing-user\" };\n }\n\n if (!resolvedSpec.tooltips[tooltipKey]) {\n return { ok: false, reason: \"missing-tooltip\" };\n }\n\n setRequestedTooltipKey(tooltipKey);\n return { ok: true };\n },\n [resolvedLoading, resolvedSpec, user]\n );\n\n const dismissTooltip = useCallback<SunboardContextValue[\"dismissTooltip\"]>(\n async (tooltipKey, options) => {\n if (resolvedLoading || !resolvedSpec) {\n return { ok: false, reason: \"not-ready\" };\n }\n\n if (!user) {\n return { ok: false, reason: \"missing-user\" };\n }\n\n if (!resolvedSpec.tooltips[tooltipKey]) {\n return { ok: false, reason: \"missing-tooltip\" };\n }\n\n const via: TooltipDismissVia = options?.via ?? \"close\";\n\n setRequestedTooltipKey((current) =>\n current === tooltipKey ? null : current\n );\n setDismissedTooltipKeys((current) => {\n const next = mergeKeys(current, [tooltipKey]);\n\n if (tooltipStorageKey && next !== current) {\n writeStoredKeys(tooltipStorageKey, next);\n }\n\n return next;\n });\n\n // Best-effort persistence, same contract as tour dismissals: never block\n // the UI on network, never surface failures to the host.\n if (hostedMode && client && experienceKey) {\n void client\n .dismissTooltip({\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n tooltipKey,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined,\n via\n })\n .catch(() => {\n // Tracking is best-effort.\n });\n }\n\n return { ok: true };\n },\n [\n client,\n currentOrigin,\n currentRoute,\n experienceKey,\n hostedMode,\n resolvedLoading,\n resolvedSpec,\n tooltipStorageKey,\n user,\n versionId\n ]\n );\n\n const markTooltipSeen = useCallback<SunboardContextValue[\"markTooltipSeen\"]>(\n (tooltipKey) => {\n if (\n !resolvedSpec?.tooltips[tooltipKey] ||\n seenTooltipKeys.current.has(tooltipKey)\n ) {\n return;\n }\n\n seenTooltipKeys.current.add(tooltipKey);\n\n if (hostedMode && client && experienceKey && user) {\n void client\n .recordTooltipSeen({\n experienceKey,\n origin: currentOrigin,\n route: currentRoute,\n tooltipKey,\n user: toRuntimeUser(user),\n versionId: versionId ?? undefined\n })\n .catch(() => {\n // Tracking is best-effort.\n });\n }\n },\n [\n client,\n currentOrigin,\n currentRoute,\n experienceKey,\n hostedMode,\n resolvedSpec,\n user,\n versionId\n ]\n );\n\n const startChecklistStep = useCallback<\n SunboardContextValue[\"startChecklistStep\"]\n >(\n async (step) => {\n if (step.action?.type === \"startTour\") {\n return startTour(step.action.tour);\n }\n\n if (step.action?.type === \"openTooltip\") {\n // Land the user where the action happens, then open the tooltip on\n // the element. The tooltip's own route is the destination of record;\n // the step's route is the fallback for an everywhere-tooltip.\n const tooltipKey = step.action.tooltip;\n const destination =\n resolvedSpec?.tooltips[tooltipKey]?.route ?? step.route;\n\n if (destination && !routeMatches(destination, currentRoute)) {\n await navigateWithFallback(destination, navigate);\n }\n\n return openTooltip(tooltipKey);\n }\n\n await navigateToStep(step);\n },\n [\n currentRoute,\n navigate,\n navigateToStep,\n openTooltip,\n resolvedSpec,\n startTour\n ]\n );\n\n const visibleHotspots = useMemo<SunboardContextValue[\"visibleHotspots\"]>(() => {\n if (!resolvedSpec || activeTour) {\n return [];\n }\n\n return Object.entries(resolvedSpec.hotspots)\n .filter(([, hotspot]) => routeMatches(hotspot.route, currentRoute))\n .filter(\n ([, hotspot]) =>\n hotspot.repeatable ||\n !effectiveCompletedTourKeys.has(hotspot.action.tour)\n )\n .map(([key, hotspot]) => ({ key, hotspot }));\n }, [activeTour, effectiveCompletedTourKeys, currentRoute, resolvedSpec]);\n\n const visibleTooltips = useMemo<SunboardContextValue[\"visibleTooltips\"]>(() => {\n if (!resolvedSpec || activeTour) {\n return [];\n }\n\n const requestedTooltip = requestedTooltipKey\n ? resolvedSpec.tooltips[requestedTooltipKey]\n : undefined;\n\n if (requestedTooltipKey && requestedTooltip) {\n // The user asked for this one. While it waits for its route (navigation\n // in flight), nothing else competes for the slot; a prior dismissal\n // does not suppress an explicit request.\n if (!routeMatches(requestedTooltip.route, currentRoute)) {\n return [];\n }\n\n return [\n { key: requestedTooltipKey, requested: true, tooltip: requestedTooltip }\n ];\n }\n\n return Object.entries(resolvedSpec.tooltips)\n .filter(\n ([key, tooltip]) =>\n routeMatches(tooltip.route, currentRoute) &&\n !effectiveDismissedTooltipKeys.has(key)\n )\n .map(([key, tooltip]) => ({ key, requested: false, tooltip }));\n }, [\n activeTour,\n currentRoute,\n effectiveDismissedTooltipKeys,\n requestedTooltipKey,\n resolvedSpec\n ]);\n\n const value = useMemo<SunboardContextValue>(\n () => ({\n spec: resolvedSpec,\n user: effectiveUser,\n loading: resolvedLoading,\n route: currentRoute,\n completedStepKeys: effectiveCompletedStepKeys,\n completedTourKeys: effectiveCompletedTourKeys,\n completedCount,\n totalSteps,\n activeTour,\n track,\n submitSurvey,\n startTour,\n dismissTour,\n advanceTour,\n navigateToStep,\n startChecklistStep,\n completeChecklistStep,\n visibleHotspots,\n dismissedTooltipKeys: effectiveDismissedTooltipKeys,\n visibleTooltips,\n openTooltip,\n dismissTooltip,\n markTooltipSeen\n }),\n [\n resolvedSpec,\n effectiveUser,\n resolvedLoading,\n currentRoute,\n effectiveCompletedStepKeys,\n effectiveCompletedTourKeys,\n completedCount,\n totalSteps,\n activeTour,\n track,\n submitSurvey,\n startTour,\n dismissTour,\n advanceTour,\n navigateToStep,\n startChecklistStep,\n completeChecklistStep,\n visibleHotspots,\n effectiveDismissedTooltipKeys,\n visibleTooltips,\n openTooltip,\n dismissTooltip,\n markTooltipSeen\n ]\n );\n\n return (\n <SunboardContext.Provider value={value}>\n {children}\n </SunboardContext.Provider>\n );\n}\n\nfunction useSunboardBootstrap(\n client: SunboardRuntimeClient | null,\n options: {\n enabled: boolean;\n identities?: IdentitiesProp;\n nonce: number;\n origin?: string;\n route: string;\n user?: SunboardUser | null;\n }\n): BootstrapState {\n const { enabled, identities: identitiesProp, nonce, origin, route, user } =\n options;\n // Read through a ref so the effect always sees the latest prop without the\n // object's identity churning the dep array (mirrors the `user` handling:\n // identities influence the next bootstrap, they don't trigger one).\n const identitiesRef = useRef(identitiesProp);\n identitiesRef.current = identitiesProp;\n const [state, setState] = useState<BootstrapState>({\n attributes: null,\n experience: null,\n loading: false,\n survey: null\n });\n const warnedSdkAdvisory = useRef(false);\n\n useEffect(() => {\n if (!client || !enabled || !user?.id) {\n setState({\n attributes: null,\n experience: null,\n loading: false,\n survey: null\n });\n return;\n }\n\n // TODO: plumb `controller.signal` through SunboardRuntimeClient.post into\n // the underlying fetch so aborted requests cancel at the network layer. In\n // React 19 dev StrictMode the provider mounts twice; the first effect's\n // fetch keeps flying and can surface as `TypeError: Failed to fetch` in the\n // browser console. Harmless (we guard with signal.aborted before touching\n // state) but noisy in dev.\n const controller = new AbortController();\n setState({\n attributes: null,\n experience: null,\n loading: true,\n survey: null\n });\n\n // Providers are probed at request time, on every bootstrap: the id can\n // change when an anonymous visitor identifies, and a snippet that loaded\n // late is picked up by the next bootstrap.\n const identities = resolveIdentities(identitiesRef.current);\n\n client\n .bootstrap({\n origin,\n route,\n user: toRuntimeUser(user),\n ...(identities ? { identities } : {})\n })\n .then((response) => {\n if (controller.signal.aborted) {\n return;\n }\n\n if (\n response.experiences.length === 0 &&\n !response.survey &&\n isDevEnvironment()\n ) {\n console.warn(\n `[sunboard] bootstrap returned no active experiences for route \"${route}\". Check the dashboard for an enabled deployment whose trigger.route matches (the publishable key selects the environment).`\n );\n }\n\n if (\n response.sdkAdvisory &&\n isDevEnvironment() &&\n !warnedSdkAdvisory.current\n ) {\n warnedSdkAdvisory.current = true;\n console.warn(`[sunboard] ${response.sdkAdvisory.message}`);\n }\n\n setState({\n attributes: response.attributes ?? null,\n experience: response.experiences[0] ?? null,\n loading: false,\n survey: response.survey ?? null\n });\n })\n .catch((error: unknown) => {\n if (controller.signal.aborted) {\n return;\n }\n\n if (isDevEnvironment()) {\n console.warn(\"[sunboard] bootstrap failed\", error);\n }\n\n setState({\n attributes: null,\n experience: null,\n loading: false,\n survey: null\n });\n });\n\n // The bootstrap often races the analytics snippet: posthog-js typically\n // initializes in a parent effect that runs after this one, so the probe\n // above finds nothing on the first (and usually only) bootstrap of a\n // session. Keep probing briefly and push a late capture through\n // `identify`; the server merges it per provider key, same as bootstrap.\n const cancelLateCapture = scheduleLateIdentityCapture({\n client,\n identitiesRef,\n origin,\n sent: identities,\n user\n });\n\n return () => {\n controller.abort();\n cancelLateCapture();\n };\n // user.id is the identity key; we deliberately do not refetch when the\n // user object reference changes without an id change. `route` is\n // intentionally excluded so navigation does not wipe the active experience\n // — per-route visibility is handled by the runtime via step/tour/hotspot\n // route matching.\n // `nonce` is included so an explicit refreshBootstrap() (e.g. after a survey\n // submit) refetches even though client/user/route are unchanged.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, enabled, nonce, origin, user?.id]);\n\n return state;\n}\n\n/** Late-capture probe cadence: ~10s of 1s ticks covers slow analytics init. */\nconst IDENTITY_CAPTURE_INTERVAL_MS = 1_000;\nconst IDENTITY_CAPTURE_MAX_ATTEMPTS = 10;\n\n/**\n * Keep probing for analytics-provider ids the bootstrap missed, and push the\n * first find through `identify`. One send per bootstrap: after a successful\n * capture the poll stops; the next bootstrap (new user/session) starts fresh.\n */\nfunction scheduleLateIdentityCapture(options: {\n client: SunboardRuntimeClient;\n identitiesRef: { readonly current: IdentitiesProp | undefined };\n origin?: string;\n sent: Record<string, string> | undefined;\n user: SunboardUser;\n}): () => void {\n const { client, identitiesRef, origin, sent, user } = options;\n let attempts = 0;\n\n const timer = setInterval(() => {\n attempts += 1;\n\n // Read the prop through the ref on every tick so flipping it to `false`\n // mid-poll stops the capture.\n if (identitiesRef.current === false) {\n clearInterval(timer);\n return;\n }\n\n const detected = resolveIdentities(identitiesRef.current);\n if (!detected || !hasUnsentIdentity(detected, sent)) {\n if (attempts >= IDENTITY_CAPTURE_MAX_ATTEMPTS) {\n clearInterval(timer);\n }\n return;\n }\n\n clearInterval(timer);\n client\n .identify({\n identities: detected,\n user: toRuntimeUser(user),\n ...(origin ? { origin } : {})\n })\n .catch((error: unknown) => {\n if (isDevEnvironment()) {\n console.warn(\"[sunboard] late identity capture failed\", error);\n }\n });\n }, IDENTITY_CAPTURE_INTERVAL_MS);\n\n return () => clearInterval(timer);\n}\n\nfunction hasUnsentIdentity(\n detected: Record<string, string>,\n sent: Record<string, string> | undefined\n): boolean {\n return Object.entries(detected).some(([provider, id]) => sent?.[provider] !== id);\n}\n\nfunction applyProgress(\n progress: RuntimeProgress,\n setSteps: Dispatch<SetStateAction<Set<string>>>,\n setTours: Dispatch<SetStateAction<Set<string>>>,\n setDismissedTooltips?: Dispatch<SetStateAction<Set<string>>>\n) {\n setSteps((current) => mergeKeys(current, progress.completedStepKeys));\n setTours((current) => mergeKeys(current, progress.completedTourKeys));\n setDismissedTooltips?.((current) =>\n mergeKeys(current, progress.dismissedTooltipKeys)\n );\n}\n\nfunction mergeKeys(current: Set<string>, incoming: string[]): Set<string> {\n if (incoming.length === 0) {\n return current;\n }\n\n const next = new Set(current);\n\n for (const key of incoming) {\n next.add(key);\n }\n\n if (next.size === current.size) {\n return current;\n }\n\n return next;\n}\n\nfunction isDevEnvironment(): boolean {\n if (typeof process === \"undefined\" || !process.env) {\n return false;\n }\n\n return process.env.NODE_ENV !== \"production\";\n}\n\nfunction toRuntimeUser(user: SunboardUser) {\n return {\n email: user.email ?? undefined,\n group: user.group ?? undefined,\n id: user.id,\n properties: user.properties\n };\n}\n\nfunction completedToursStorageKey(specKey: string, userId: string): string {\n return `sunboard:${specKey}:${userId}:completed-tours`;\n}\n\nfunction dismissedTooltipsStorageKey(specKey: string, userId: string): string {\n return `sunboard:${specKey}:${userId}:dismissed-tooltips`;\n}\n\nfunction getBrowserRoute(): string {\n if (typeof window === \"undefined\") {\n return \"/\";\n }\n\n return `${window.location.pathname}${window.location.search}`;\n}\n\nfunction getBrowserOrigin(): string | undefined {\n if (typeof window === \"undefined\") {\n return undefined;\n }\n\n return window.location.origin;\n}\n\nfunction readStoredKeys(storageKey: string): Set<string> {\n if (typeof window === \"undefined\") {\n return new Set();\n }\n\n try {\n const rawValue = window.localStorage.getItem(storageKey);\n const parsed = rawValue ? (JSON.parse(rawValue) as unknown) : [];\n\n if (!Array.isArray(parsed)) {\n return new Set();\n }\n\n return new Set(parsed.filter((value): value is string => typeof value === \"string\"));\n } catch {\n return new Set();\n }\n}\n\nfunction writeStoredKeys(storageKey: string, keys: Set<string>) {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify([...keys].sort()));\n } catch {\n // Ignore storage failures; persistence should never break the host app.\n }\n}\n","import type { SunboardUser } from \"../hooks/sunboard-context\";\n\nconst TOKEN_PATTERN =\n /\\{\\{\\s*user\\.([\\w][\\w.]*)(?:\\s*\\|\\s*(?:\"([^\"]*)\"|'([^']*)'))?\\s*\\}\\}/g;\n\ntype FlatUser = Record<string, unknown>;\n\nfunction flattenUser(user: SunboardUser | null | undefined): FlatUser {\n if (!user) return {};\n const properties = (user.properties ?? {}) as Record<string, unknown>;\n const flat: FlatUser = { ...properties };\n flat.id = user.id;\n if (user.email !== undefined) flat.email = user.email;\n if (user.group !== undefined) flat.group = user.group;\n return flat;\n}\n\nfunction lookupPath(root: FlatUser, path: string): unknown {\n const segments = path.split(\".\");\n let current: unknown = root;\n for (const segment of segments) {\n if (current === null || current === undefined) return undefined;\n if (typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[segment];\n }\n return current;\n}\n\nfunction coerceValue(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === \"string\") return value.length > 0 ? value : null;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n return null;\n}\n\nexport function interpolateTokens(\n template: string,\n user: SunboardUser | null | undefined\n): string {\n if (!template || !template.includes(\"{{\")) return template;\n const flat = flattenUser(user);\n return template.replace(\n TOKEN_PATTERN,\n (_match, path: string, doubleQuoted?: string, singleQuoted?: string) => {\n const resolved = coerceValue(lookupPath(flat, path));\n if (resolved !== null) return resolved;\n const fallback = doubleQuoted ?? singleQuoted;\n return fallback ?? \"\";\n }\n );\n}\n\nexport function extractUserTokens(template: string): string[] {\n if (!template || !template.includes(\"{{\")) return [];\n const paths: string[] = [];\n TOKEN_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = TOKEN_PATTERN.exec(template)) !== null) {\n const path = match[1];\n if (path) paths.push(path);\n }\n return paths;\n}\n","import { useContext } from \"react\";\nimport { SunboardContext, type SunboardContextValue } from \"./sunboard-context\";\n\nexport function useSunboard(): SunboardContextValue {\n const context = useContext(SunboardContext);\n\n if (!context) {\n throw new Error(\"useSunboard must be used within SunboardProvider.\");\n }\n\n return context;\n}\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { SunboardStep } from \"@sunboard/core\";\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\nimport { useSunboard } from \"./useSunboard\";\n\nexport type ChecklistViewStep = {\n key: string;\n title: string;\n description?: string;\n badge?: string;\n actionLabel?: string;\n /**\n * True when the user can complete this step by checking it off (the spec\n * default). False (`manual: false` in the spec) means the step completes\n * only through its instrumented rule; render its checkbox non-interactive.\n */\n manuallyCompletable: boolean;\n};\n\nexport type ChecklistCompletion = {\n title: string;\n subtitle?: string;\n message?: string;\n dismissAfterMs?: number;\n};\n\nexport type UseChecklistResult = {\n visible: boolean;\n title?: string;\n subtitle?: string;\n steps: ChecklistViewStep[];\n completedStepKeys: ReadonlySet<string>;\n completedCount: number;\n totalSteps: number;\n remainingCount: number;\n progress: number;\n isCompleted: (key: string) => boolean;\n hasAction: (step: ChecklistViewStep) => boolean;\n startStep: (step: ChecklistViewStep) => void;\n /**\n * Mark the step complete because the user checked it off. A no-op for\n * steps that are not manually completable or already complete.\n */\n completeStep: (step: ChecklistViewStep) => void;\n completion?: ChecklistCompletion;\n dismissCompletion: () => void;\n};\n\nfunction actionLabelFor(step: SunboardStep): string | undefined {\n if (step.action?.type === \"startTour\") return \"Start\";\n if (step.action?.type === \"openTooltip\") return \"Show me\";\n if (step.route) return \"Open\";\n return undefined;\n}\n\nfunction interpolateOptional(\n text: string | undefined,\n user: Parameters<typeof interpolateTokens>[1]\n): string | undefined {\n if (!text) return undefined;\n const interpolated = interpolateTokens(text, user);\n return interpolated || undefined;\n}\n\nexport function useChecklist(): UseChecklistResult {\n const {\n spec,\n user,\n loading,\n completedStepKeys,\n completedCount,\n totalSteps,\n startChecklistStep,\n completeChecklistStep\n } = useSunboard();\n\n const [dismissedKey, setDismissedKey] = useState<string | null>(null);\n\n // A guide renders whatever surfaces it actually declares, so the checklist\n // shows whenever the spec carries checklist steps — not based on the type.\n // (Surveys deliver through their own surface and never carry a checklist.)\n const hasChecklist = Boolean(spec && spec.checklist?.steps.length);\n const allCompleted = totalSteps > 0 && completedCount >= totalSteps;\n const ready = !loading && !!user && !!spec && hasChecklist;\n\n // The completion screen celebrates the *transition* from incomplete to\n // complete in this session — not the steady \"already done\" state. The server\n // already knows per-user completion via bootstrap, so a user who finished\n // on another device sees `allCompleted === true` from the first ready\n // render and we suppress the celebration. We only show it if we observed\n // them being incomplete first.\n //\n // Scoped to (spec, user) so switching either restarts the observation.\n const specKey = spec?.key ?? null;\n const userId = user?.id ?? null;\n const observationKey =\n ready && specKey && userId ? `${specKey}:${userId}` : null;\n const observationRef = useRef<{ key: string; sawIncomplete: boolean } | null>(\n null\n );\n if (observationKey && observationRef.current?.key !== observationKey) {\n observationRef.current = {\n key: observationKey,\n sawIncomplete: !allCompleted\n };\n } else if (observationKey && observationRef.current && !allCompleted) {\n observationRef.current.sawIncomplete = true;\n }\n const sawIncompleteFirst = observationRef.current?.sawIncomplete ?? false;\n\n const dismissed = dismissedKey !== null && dismissedKey === observationKey;\n\n const rawSteps = useMemo<SunboardStep[]>(\n () => spec?.checklist?.steps ?? [],\n [spec]\n );\n\n const title = useMemo(\n () => interpolateOptional(spec?.title, user ?? null),\n [spec?.title, user]\n );\n\n const subtitle = useMemo(\n () => interpolateOptional(spec?.subtitle, user ?? null),\n [spec?.subtitle, user]\n );\n\n const completion = useMemo<ChecklistCompletion | undefined>(() => {\n const raw = spec?.completion;\n if (!raw) return undefined;\n const interpolatedTitle = interpolateOptional(raw.title, user ?? null);\n if (!interpolatedTitle) return undefined;\n return {\n title: interpolatedTitle,\n subtitle: interpolateOptional(raw.subtitle, user ?? null),\n message: interpolateOptional(raw.message, user ?? null),\n dismissAfterMs: raw.dismissAfterMs\n };\n }, [spec?.completion, user]);\n\n const showingCompletion =\n ready && allCompleted && !!completion && sawIncompleteFirst && !dismissed;\n const visible = (ready && !allCompleted) || showingCompletion;\n\n const steps = useMemo<ChecklistViewStep[]>(() => {\n return rawSteps.map((step) => ({\n key: step.key,\n title: interpolateTokens(step.title, user ?? null),\n description: step.description\n ? interpolateTokens(step.description, user ?? null)\n : undefined,\n badge: step.badge\n ? interpolateTokens(step.badge, user ?? null)\n : undefined,\n actionLabel: actionLabelFor(step),\n manuallyCompletable: step.manual !== false\n }));\n }, [rawSteps, user]);\n\n const stepsByKey = useMemo(() => {\n const map = new Map<string, SunboardStep>();\n for (const step of rawSteps) {\n map.set(step.key, step);\n }\n return map;\n }, [rawSteps]);\n\n const remainingCount = Math.max(totalSteps - completedCount, 0);\n const progress =\n totalSteps > 0 ? Math.round((completedCount / totalSteps) * 100) : 0;\n\n const dismissCompletion = useCallback(() => {\n if (observationKey) {\n setDismissedKey(observationKey);\n }\n }, [observationKey]);\n\n return {\n visible,\n title,\n subtitle,\n steps,\n completedStepKeys,\n completedCount,\n totalSteps,\n remainingCount,\n progress,\n isCompleted: (key) => completedStepKeys.has(key),\n hasAction: (step) => step.actionLabel !== undefined,\n startStep: (step) => {\n const raw = stepsByKey.get(step.key);\n if (raw) {\n void startChecklistStep(raw);\n }\n },\n completeStep: (step) => {\n if (step.manuallyCompletable && !completedStepKeys.has(step.key)) {\n void completeChecklistStep(step.key);\n }\n },\n completion: showingCompletion ? completion : undefined,\n dismissCompletion\n };\n}\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { SurveyQuestionType } from \"@sunboard/core\";\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\nimport { useSunboard } from \"./useSunboard\";\n\nexport type SurveyViewOption = {\n value: string;\n label: string;\n};\n\nexport type SurveyViewQuestion = {\n key: string;\n prompt: string;\n type: SurveyQuestionType;\n required: boolean;\n options: SurveyViewOption[];\n};\n\nexport type SurveyViewStep = {\n title?: string;\n subtitle?: string;\n questions: SurveyViewQuestion[];\n};\n\nexport type UseSurveyResult = {\n visible: boolean;\n title?: string;\n subtitle?: string;\n /** Every question across all steps — for all-at-once renderers (the built-in modal). */\n questions: SurveyViewQuestion[];\n /** The authored step grouping (one question per step by default). */\n steps: SurveyViewStep[];\n /** Hook-owned navigation. */\n stepIndex: number;\n totalSteps: number;\n currentStep?: SurveyViewStep;\n progress: number; // 0..100, by step\n isFirstStep: boolean;\n isLastStep: boolean;\n /** True when the current step's required questions are answered. */\n canAdvance: boolean;\n /** Advance to the next step, or submit on the last step. */\n next: () => void;\n back: () => void;\n answers: Record<string, unknown>;\n setAnswer: (key: string, value: string) => void;\n toggleAnswer: (key: string, value: string) => void;\n isAnswered: (key: string) => boolean;\n /** True when every required question (across all steps) is answered. */\n canSubmit: boolean;\n submitting: boolean;\n error: string | null;\n submit: () => Promise<void>;\n};\n\nfunction interpolateOptional(\n text: string | undefined,\n user: Parameters<typeof interpolateTokens>[1]\n): string | undefined {\n if (!text) return undefined;\n const interpolated = interpolateTokens(text, user);\n return interpolated || undefined;\n}\n\nfunction answered(value: unknown): boolean {\n if (value === undefined || value === null) return false;\n if (typeof value === \"string\") return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n}\n\nexport function useSurvey(): UseSurveyResult {\n const { spec, user, loading, submitSurvey } = useSunboard();\n\n const [answers, setAnswers] = useState<Record<string, unknown>>({});\n const [submitting, setSubmitting] = useState(false);\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [stepIndex, setStepIndex] = useState(0);\n\n const isSurvey = Boolean(spec && spec.type === \"survey\" && spec.survey);\n const ready = !loading && !!user && isSurvey;\n\n const title = useMemo(\n () => interpolateOptional(spec?.title, user ?? null),\n [spec?.title, user]\n );\n const subtitle = useMemo(\n () => interpolateOptional(spec?.subtitle, user ?? null),\n [spec?.subtitle, user]\n );\n\n const steps = useMemo<SurveyViewStep[]>(() => {\n const rawSteps = spec?.survey?.steps ?? [];\n return rawSteps.map((step) => ({\n title: interpolateOptional(step.title, user ?? null),\n subtitle: interpolateOptional(step.subtitle, user ?? null),\n questions: step.questions.map((question) => ({\n key: question.key,\n prompt: interpolateTokens(question.prompt, user ?? null),\n type: question.type,\n required: question.required,\n options: (question.options ?? []).map((option) => ({\n value: option.value,\n label: interpolateTokens(option.label, user ?? null)\n }))\n }))\n }));\n }, [spec?.survey, user]);\n\n const questions = useMemo<SurveyViewQuestion[]>(\n () => steps.flatMap((step) => step.questions),\n [steps]\n );\n\n // Restart at the first step whenever the question set changes (a new survey).\n const signature = questions.map((question) => question.key).join(\"|\");\n const signatureRef = useRef(signature);\n if (signatureRef.current !== signature) {\n signatureRef.current = signature;\n if (stepIndex !== 0) setStepIndex(0);\n }\n\n const totalSteps = steps.length;\n const safeIndex =\n totalSteps > 0 ? Math.min(stepIndex, totalSteps - 1) : 0;\n const currentStep = steps[safeIndex];\n\n const setAnswer = useCallback((key: string, value: string) => {\n setAnswers((current) => ({ ...current, [key]: value }));\n }, []);\n\n const toggleAnswer = useCallback((key: string, value: string) => {\n setAnswers((current) => {\n const existing = Array.isArray(current[key])\n ? (current[key] as string[])\n : [];\n const next = existing.includes(value)\n ? existing.filter((item) => item !== value)\n : [...existing, value];\n return { ...current, [key]: next };\n });\n }, []);\n\n const isAnswered = useCallback(\n (key: string) => answered(answers[key]),\n [answers]\n );\n\n const canSubmit = useMemo(() => {\n if (!ready || submitting) return false;\n return questions\n .filter((question) => question.required)\n .every((question) => answered(answers[question.key]));\n }, [answers, questions, ready, submitting]);\n\n const canAdvance = useMemo(() => {\n if (!currentStep) return false;\n return currentStep.questions\n .filter((question) => question.required)\n .every((question) => answered(answers[question.key]));\n }, [answers, currentStep]);\n\n const submit = useCallback(async () => {\n if (!canSubmit) return;\n setSubmitting(true);\n setError(null);\n\n const result = await submitSurvey(answers);\n setSubmitting(false);\n\n if (result.ok) {\n setSubmitted(true);\n return;\n }\n\n setError(\n result.reason === \"network-error\"\n ? \"Something went wrong. Please try again.\"\n : \"Survey is not ready.\"\n );\n }, [answers, canSubmit, submitSurvey]);\n\n const isLastStep = totalSteps === 0 || safeIndex >= totalSteps - 1;\n const isFirstStep = safeIndex === 0;\n\n const next = useCallback(() => {\n if (isLastStep) {\n void submit();\n } else {\n setStepIndex(safeIndex + 1);\n }\n }, [isLastStep, safeIndex, submit]);\n\n const back = useCallback(() => {\n setStepIndex((current) => Math.max(0, current - 1));\n }, []);\n\n return {\n visible: ready && !submitted,\n title,\n subtitle,\n questions,\n steps,\n stepIndex: safeIndex,\n totalSteps,\n currentStep,\n progress: totalSteps > 0 ? ((safeIndex + 1) / totalSteps) * 100 : 0,\n isFirstStep,\n isLastStep,\n canAdvance,\n next,\n back,\n answers,\n setAnswer,\n toggleAnswer,\n isAnswered,\n canSubmit,\n submitting,\n error,\n submit\n };\n}\n","\nexport const CheckIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-check-icon lucide-check\"><path d=\"M20 6 9 17l-5-5\" /></svg>\n}\n\nexport const ListIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-list-icon lucide-list\"><path d=\"M3 12h.01\" /><path d=\"M3 18h.01\" /><path d=\"M3 6h.01\" /><path d=\"M8 12h13\" /><path d=\"M8 18h13\" /><path d=\"M8 6h13\" /></svg>\n}\n\nexport const XIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-x-icon lucide-x\"><path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" /></svg>\n}\n\nexport const ChevronRightIcon = () => {\n return <svg xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-right-icon lucide-chevron-right\"><path d=\"m9 18 6-6-6-6\" /></svg>\n}\n","import { CheckIcon, ChevronRightIcon } from \"../icons\";\nimport type { ChecklistViewStep } from \"../hooks/useChecklist\";\n\ntype ChecklistStepProps = {\n completed: boolean;\n onComplete: () => void;\n onStart: () => void;\n step: ChecklistViewStep;\n};\n\nexport function ChecklistStep({\n completed,\n onComplete,\n onStart,\n step\n}: ChecklistStepProps) {\n const hasAction = step.actionLabel !== undefined;\n const checkable = step.manuallyCompletable && !completed;\n\n // The checkbox and the action area are sibling interactive elements (a\n // button cannot nest inside a button), so checking a step off never also\n // fires the step's action.\n const status = checkable ? (\n <button\n className=\"sunboard-step-status\"\n type=\"button\"\n onClick={onComplete}\n aria-label={`Mark ${step.title} complete`}\n >\n <CheckIcon />\n </button>\n ) : (\n <span\n className=\"sunboard-step-status\"\n data-locked={!completed && !step.manuallyCompletable ? \"true\" : undefined}\n aria-label={\n completed\n ? \"Completed\"\n : step.manuallyCompletable\n ? \"Incomplete\"\n : \"Completes automatically\"\n }\n >\n {completed ? <CheckIcon /> : null}\n </span>\n );\n\n const content = (\n <div className=\"sunboard-step-content\">\n <div className=\"sunboard-step-heading\">\n <h3 className=\"sunboard-step-title\">{step.title}</h3>\n {step.badge ? (\n <span className=\"sunboard-step-badge\">{step.badge}</span>\n ) : null}\n </div>\n\n {step.description ? (\n <p className=\"sunboard-step-description\">{step.description}</p>\n ) : null}\n </div>\n );\n\n return (\n <li className=\"sunboard-step\" data-completed={completed}>\n <div\n className=\"sunboard-step-row\"\n data-interactive={hasAction || checkable ? \"true\" : undefined}\n >\n {status}\n {hasAction ? (\n <button\n className=\"sunboard-step-main\"\n type=\"button\"\n onClick={onStart}\n aria-label={`${step.actionLabel} ${step.title}`}\n >\n {content}\n <span className=\"sunboard-step-arrow\" aria-hidden=\"true\">\n <ChevronRightIcon />\n </span>\n </button>\n ) : (\n content\n )}\n </div>\n </li>\n );\n}\n","import { useMemo } from \"react\";\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\nimport { useSunboard } from \"./useSunboard\";\n\nexport function useInterpolated(text: string): string;\nexport function useInterpolated(text: string | undefined): string | undefined;\nexport function useInterpolated(text: string | undefined): string | undefined {\n const { user } = useSunboard();\n return useMemo(() => {\n if (text === undefined) return undefined;\n return interpolateTokens(text, user);\n }, [text, user]);\n}\n","import { useEffect, useState } from \"react\";\n\nexport type TargetElementState = {\n element: HTMLElement | null;\n timedOut: boolean;\n};\n\nexport function useTargetElement(\n selector: string | null,\n enabled: boolean,\n timeoutMs = 5_000\n): TargetElementState {\n const [state, setState] = useState<TargetElementState>({\n element: null,\n timedOut: false\n });\n\n useEffect(() => {\n if (!enabled || !selector) {\n setState({ element: null, timedOut: false });\n return;\n }\n\n let cancelled = false;\n const startedAt = Date.now();\n\n const findTarget = () => {\n if (cancelled) {\n return;\n }\n\n const element = document.querySelector(selector);\n\n if (element instanceof HTMLElement) {\n setState({ element, timedOut: false });\n return;\n }\n\n if (Date.now() - startedAt >= timeoutMs) {\n setState({ element: null, timedOut: true });\n return;\n }\n\n window.setTimeout(findTarget, 100);\n };\n\n setState({ element: null, timedOut: false });\n findTarget();\n\n return () => {\n cancelled = true;\n };\n }, [enabled, selector, timeoutMs]);\n\n return state;\n}\n","import {\n FloatingPortal,\n autoUpdate,\n offset,\n useFloating\n} from \"@floating-ui/react\";\nimport { useEffect } from \"react\";\nimport type { SunboardHotspot } from \"@sunboard/core\";\nimport { useInterpolated } from \"../hooks/useInterpolated\";\nimport { useSunboard } from \"../hooks/useSunboard\";\nimport { useTargetElement } from \"../hooks/useTargetElement\";\n\ntype HotspotProps = {\n hotspot: SunboardHotspot;\n hotspotKey: string;\n};\n\nconst HOTSPOT_RADIUS_PX = 9;\n\nexport function SunboardHotspots() {\n const { visibleHotspots } = useSunboard();\n\n return (\n <>\n {visibleHotspots.map(({ key, hotspot }) => (\n <Hotspot hotspot={hotspot} hotspotKey={key} key={key} />\n ))}\n </>\n );\n}\n\nfunction Hotspot({ hotspot, hotspotKey }: HotspotProps) {\n const { startTour } = useSunboard();\n const label = useInterpolated(hotspot.label);\n const target = useTargetElement(hotspot.target, true);\n const { refs, floatingStyles } = useFloating({\n placement: hotspot.placement,\n strategy: \"fixed\",\n middleware: [offset(-HOTSPOT_RADIUS_PX)],\n whileElementsMounted: autoUpdate\n });\n\n useEffect(() => {\n if (!target.element) {\n return;\n }\n\n refs.setReference(target.element);\n }, [refs, target.element]);\n\n if (!target.element) {\n return null;\n }\n\n return (\n <FloatingPortal>\n <button\n aria-label={label ?? `Start ${hotspot.action.tour} tour`}\n className=\"sunboard-hotspot\"\n data-pulse={hotspot.pulse}\n data-sunboard-hotspot={hotspotKey}\n onClick={() => {\n void startTour(hotspot.action.tour);\n }}\n ref={refs.setFloating}\n style={floatingStyles}\n type=\"button\"\n >\n <span className=\"sunboard-hotspot-dot\" />\n </button>\n </FloatingPortal>\n );\n}\n","import {\n arrow,\n autoUpdate,\n FloatingArrow,\n FloatingPortal,\n flip,\n offset,\n shift,\n useFloating\n} from \"@floating-ui/react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { SunboardTooltip } from \"@sunboard/core\";\nimport { useInterpolated } from \"../hooks/useInterpolated\";\nimport { useSunboard } from \"../hooks/useSunboard\";\nimport { useTargetElement } from \"../hooks/useTargetElement\";\n\ntype TooltipProps = {\n tooltip: SunboardTooltip;\n tooltipKey: string;\n requested: boolean;\n onTargetMissing: () => void;\n};\n\n/**\n * Renders at most one tooltip: the first visible candidate whose target\n * resolves. A candidate whose selector never appears is skipped for the rest\n * of the session (without recording a dismissal — the user rejected nothing),\n * so one stale selector can't block the tooltips behind it.\n */\nexport function SunboardTooltips() {\n const { visibleTooltips } = useSunboard();\n const [skippedKeys, setSkippedKeys] = useState<Set<string>>(() => new Set());\n\n const active =\n visibleTooltips.find(({ key }) => !skippedKeys.has(key)) ?? null;\n\n if (!active) {\n return null;\n }\n\n return (\n <Tooltip\n key={active.key}\n onTargetMissing={() =>\n setSkippedKeys((current) => new Set(current).add(active.key))\n }\n requested={active.requested}\n tooltip={active.tooltip}\n tooltipKey={active.key}\n />\n );\n}\n\nfunction Tooltip({\n tooltip,\n tooltipKey,\n requested,\n onTargetMissing\n}: TooltipProps) {\n const { dismissTooltip, markTooltipSeen, startTour } = useSunboard();\n const arrowRef = useRef<SVGSVGElement | null>(null);\n const title = useInterpolated(tooltip.title);\n const body = useInterpolated(tooltip.body);\n const actionLabel = useInterpolated(tooltip.actionLabel) ?? \"Show me how\";\n const target = useTargetElement(tooltip.target, true);\n const { context, refs, floatingStyles, update } = useFloating({\n placement: tooltip.placement,\n middleware: [\n offset(12),\n flip(),\n shift({ padding: 12 }),\n arrow({ element: arrowRef })\n ],\n whileElementsMounted: autoUpdate\n });\n\n useEffect(() => {\n if (!target.timedOut) {\n return;\n }\n\n onTargetMissing();\n // `onTargetMissing` intentionally excluded: it is an inline closure whose\n // identity churns per render, and the skip must fire exactly once.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [target.timedOut]);\n\n useEffect(() => {\n if (!target.element) {\n return;\n }\n\n refs.setReference(target.element);\n void update();\n // The tooltip reached the user's eyes — this is the reach denominator for\n // \"did the nudge move the goal\".\n markTooltipSeen(tooltipKey);\n }, [markTooltipSeen, refs, target.element, tooltipKey, update]);\n\n useEffect(() => {\n const element = target.element;\n\n if (!element) {\n return;\n }\n\n // Clicking the target means the user did the thing the tooltip pointed\n // at: the nudge worked, retire it. Deferred (same as tour advancement)\n // so the app's own click handler runs before the dismissal re-renders.\n const dismissOnTargetClick = () => {\n window.setTimeout(() => {\n void dismissTooltip(tooltipKey, { via: \"target\" });\n }, 0);\n };\n\n element.addEventListener(\"click\", dismissOnTargetClick);\n\n return () => {\n element.removeEventListener(\"click\", dismissOnTargetClick);\n };\n }, [dismissTooltip, target.element, tooltipKey]);\n\n if (!target.element) {\n return null;\n }\n\n return (\n <FloatingPortal>\n <section\n aria-label={title}\n className=\"sunboard-tooltip\"\n data-sunboard-tooltip={tooltipKey}\n data-requested={requested}\n ref={refs.setFloating}\n role=\"dialog\"\n style={floatingStyles}\n >\n <FloatingArrow\n className=\"sunboard-tooltip-arrow\"\n context={context}\n fill=\"var(--sunboard-tour-background)\"\n ref={arrowRef}\n stroke=\"var(--sunboard-tour-border)\"\n strokeWidth={1}\n tipRadius={2}\n />\n <button\n aria-label={`Dismiss ${title}`}\n className=\"sunboard-tooltip-close\"\n onClick={() => void dismissTooltip(tooltipKey)}\n type=\"button\"\n >\n x\n </button>\n <h2 className=\"sunboard-tooltip-title\">{title}</h2>\n {body ? <p className=\"sunboard-tooltip-body\">{body}</p> : null}\n {tooltip.action ? (\n <div className=\"sunboard-tooltip-actions\">\n <button\n className=\"sunboard-tooltip-action\"\n onClick={() => {\n // Taking the action counts as engagement AND retires the\n // tooltip; the tour then owns the screen.\n void dismissTooltip(tooltipKey, { via: \"action\" });\n void startTour(tooltip.action!.tour);\n }}\n type=\"button\"\n >\n {actionLabel}\n </button>\n </div>\n ) : null}\n </section>\n </FloatingPortal>\n );\n}\n","import {\n arrow,\n FloatingArrow,\n FloatingPortal,\n flip,\n offset,\n shift,\n useFloating\n} from \"@floating-ui/react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { routeMatches } from \"../adapters/routes\";\nimport { useInterpolated } from \"../hooks/useInterpolated\";\nimport { useSunboard } from \"../hooks/useSunboard\";\nimport { useTargetElement } from \"../hooks/useTargetElement\";\n\nexport function SunboardTour() {\n const { activeTour, advanceTour, dismissTour, route } = useSunboard();\n const arrowRef = useRef<SVGSVGElement | null>(null);\n const [spotlightRect, setSpotlightRect] = useState<DOMRect | null>(null);\n const routeReady = routeMatches(activeTour?.step.route, route ?? \"/\");\n const target = useTargetElement(\n activeTour?.step.target ?? null,\n Boolean(activeTour) && routeReady,\n 1_000\n );\n const { context, refs, floatingStyles, update } = useFloating({\n placement: activeTour?.step.placement ?? \"bottom\",\n middleware: [\n offset(16),\n flip(),\n shift({ padding: 12 }),\n arrow({ element: arrowRef })\n ]\n });\n\n useEffect(() => {\n if (!activeTour || !routeReady || !target.timedOut) {\n return;\n }\n\n // The target is missing and the user didn't reject anything, so close\n // programmatically without recording a dismissal signal.\n void dismissTour({ silent: true });\n }, [activeTour, dismissTour, routeReady, target.timedOut]);\n\n useEffect(() => {\n if (!activeTour || !target.element) {\n return;\n }\n\n refs.setReference(target.element);\n target.element.scrollIntoView({ block: \"center\", inline: \"center\" });\n void update();\n }, [activeTour, refs, target.element, update]);\n\n useEffect(() => {\n if (!activeTour || !target.element) {\n setSpotlightRect(null);\n return;\n }\n\n const updateSpotlight = () => {\n setSpotlightRect(target.element?.getBoundingClientRect() ?? null);\n };\n\n updateSpotlight();\n window.addEventListener(\"resize\", updateSpotlight);\n window.addEventListener(\"scroll\", updateSpotlight, true);\n\n target.element.setAttribute(\"data-sunboard-tour-target\", \"true\");\n\n return () => {\n window.removeEventListener(\"resize\", updateSpotlight);\n window.removeEventListener(\"scroll\", updateSpotlight, true);\n target.element?.removeAttribute(\"data-sunboard-tour-target\");\n setSpotlightRect(null);\n };\n }, [activeTour, target.element]);\n\n useEffect(() => {\n if (!activeTour || !target.element) {\n return;\n }\n\n const advanceOnTargetClick = () => {\n window.setTimeout(() => {\n void advanceTour();\n }, 0);\n };\n\n target.element.addEventListener(\"click\", advanceOnTargetClick);\n\n return () => {\n target.element?.removeEventListener(\"click\", advanceOnTargetClick);\n };\n }, [activeTour, advanceTour, target.element]);\n\n const tourName = useInterpolated(activeTour?.name);\n const stepTitle = useInterpolated(activeTour?.step.title);\n const stepBody = useInterpolated(activeTour?.step.body);\n\n if (!activeTour) {\n return null;\n }\n\n if (!routeReady || !target.element) {\n return null;\n }\n\n const isLastStep = activeTour.stepIndex === activeTour.totalSteps - 1;\n\n return (\n <FloatingPortal>\n <SunboardTourScrim rect={spotlightRect} />\n <section\n aria-label={`${tourName} tour step`}\n className=\"sunboard-tour\"\n ref={refs.setFloating}\n role=\"dialog\"\n style={floatingStyles}\n >\n <FloatingArrow\n className=\"sunboard-tour-arrow\"\n context={context}\n fill=\"var(--sunboard-tour-background)\"\n ref={arrowRef}\n stroke=\"var(--sunboard-tour-border)\"\n strokeWidth={1}\n tipRadius={2}\n />\n <div className=\"sunboard-tour-meta\">\n {activeTour.stepIndex + 1} / {activeTour.totalSteps}\n </div>\n <h2 className=\"sunboard-tour-title\">{stepTitle}</h2>\n {stepBody ? (\n <p className=\"sunboard-tour-body\">{stepBody}</p>\n ) : null}\n <div className=\"sunboard-tour-actions\">\n <button\n className=\"sunboard-tour-secondary\"\n onClick={() => void dismissTour()}\n type=\"button\"\n >\n Dismiss\n </button>\n <button\n className=\"sunboard-tour-primary\"\n onClick={() => void advanceTour()}\n type=\"button\"\n >\n {isLastStep ? \"Finish\" : \"Next\"}\n </button>\n </div>\n </section>\n </FloatingPortal>\n );\n}\n\nfunction SunboardTourScrim({ rect }: { rect: DOMRect | null }) {\n if (!rect) {\n return <div className=\"sunboard-tour-scrim\" />;\n }\n\n const padding = 8;\n const radius = 12;\n const left = Math.max(rect.left - padding, 0);\n const top = Math.max(rect.top - padding, 0);\n const width = rect.width + padding * 2;\n const height = rect.height + padding * 2;\n const maskId = \"sunboard-tour-spotlight-mask\";\n\n return (\n <svg\n aria-hidden=\"true\"\n className=\"sunboard-tour-scrim sunboard-tour-spotlight\"\n focusable=\"false\"\n >\n <defs>\n <mask id={maskId}>\n <rect fill=\"white\" height=\"100%\" width=\"100%\" x=\"0\" y=\"0\" />\n <rect\n fill=\"black\"\n height={height}\n rx={radius}\n ry={radius}\n width={width}\n x={left}\n y={top}\n />\n </mask>\n </defs>\n <rect\n fill=\"var(--sunboard-overlay-color)\"\n height=\"100%\"\n mask={`url(#${maskId})`}\n width=\"100%\"\n x=\"0\"\n y=\"0\"\n />\n </svg>\n );\n}\n","import { useEffect, useState } from \"react\";\nimport { useChecklist } from \"../hooks/useChecklist\";\nimport { useSurvey } from \"../hooks/useSurvey\";\nimport { ListIcon, XIcon } from \"../icons\";\nimport { ChecklistStep } from \"./ChecklistStep\";\nimport { SunboardHotspots } from \"./SunboardHotspots\";\nimport { SunboardTooltips } from \"./SunboardTooltips\";\nimport { SunboardTour } from \"./SunboardTour\";\n\nexport function SunboardChecklist() {\n const [open, setOpen] = useState(true);\n const {\n visible,\n title,\n subtitle,\n steps,\n completedCount,\n totalSteps,\n remainingCount,\n progress,\n isCompleted,\n startStep,\n completeStep,\n completion,\n dismissCompletion\n } = useChecklist();\n\n // Auto-dismiss the completion screen after the spec-author-specified delay.\n // Lives in the widget rather than the hook so custom checklists can choose\n // their own timing UX (e.g. dismiss on tab change) without inheriting ours.\n useEffect(() => {\n if (!completion?.dismissAfterMs) return;\n const handle = window.setTimeout(dismissCompletion, completion.dismissAfterMs);\n return () => window.clearTimeout(handle);\n }, [completion, dismissCompletion]);\n\n if (!visible || !title) {\n return null;\n }\n\n if (completion) {\n return (\n <aside\n className=\"sunboard-root sunboard-root-completion\"\n aria-label={`${title} complete`}\n role=\"status\"\n aria-live=\"polite\"\n >\n <button\n className=\"sunboard-close sunboard-completion-close\"\n type=\"button\"\n onClick={dismissCompletion}\n aria-label=\"Dismiss completion message\"\n >\n <XIcon />\n </button>\n <div className=\"sunboard-completion\">\n <div className=\"sunboard-completion-check\" aria-hidden=\"true\">\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"5 12 10 17 19 8\" />\n </svg>\n </div>\n {completion.subtitle ? (\n <p className=\"sunboard-completion-subtitle\">{completion.subtitle}</p>\n ) : null}\n <h2 className=\"sunboard-completion-title\">{completion.title}</h2>\n {completion.message ? (\n <p className=\"sunboard-completion-message\">{completion.message}</p>\n ) : null}\n </div>\n </aside>\n );\n }\n\n if (!open) {\n return (\n <button\n className=\"sunboard-launcher\"\n type=\"button\"\n onClick={() => setOpen(true)}\n aria-label={`Open ${title} checklist, ${remainingCount} tasks remaining`}\n >\n {remainingCount > 0 ? (\n <span className=\"sunboard-launcher-badge\">{remainingCount}</span>\n ) : null}\n <ListIcon />\n </button>\n );\n }\n\n return (\n <aside className=\"sunboard-root\" aria-label={`${title} checklist`}>\n <div className=\"sunboard-header\">\n <div>\n {subtitle ? <p className=\"sunboard-eyebrow\">{subtitle}</p> : null}\n <h2 className=\"sunboard-title\">{title}</h2>\n </div>\n <div className=\"sunboard-header-actions\">\n <p\n className=\"sunboard-progress-count\"\n aria-label=\"Checklist progress\"\n >\n {completedCount} / {totalSteps}\n </p>\n <button\n className=\"sunboard-close\"\n type=\"button\"\n onClick={() => setOpen(false)}\n aria-label={`Close ${title} checklist`}\n >\n <XIcon />\n </button>\n </div>\n </div>\n\n <div\n className=\"sunboard-progress\"\n role=\"progressbar\"\n aria-label={`${title} progress`}\n aria-valuemin={0}\n aria-valuemax={totalSteps}\n aria-valuenow={completedCount}\n >\n <div\n className=\"sunboard-progress-fill\"\n style={{ width: `${progress}%` }}\n />\n </div>\n\n <ol className=\"sunboard-step-list\">\n {steps.map((step) => (\n <ChecklistStep\n completed={isCompleted(step.key)}\n key={step.key}\n onComplete={() => completeStep(step)}\n onStart={() => startStep(step)}\n step={step}\n />\n ))}\n </ol>\n </aside>\n );\n}\n\nexport function SunboardSurvey() {\n const {\n visible,\n title,\n subtitle,\n currentStep,\n stepIndex,\n totalSteps,\n progress,\n isFirstStep,\n isLastStep,\n canAdvance,\n canSubmit,\n next,\n back,\n answers,\n setAnswer,\n toggleAnswer,\n submitting,\n error\n } = useSurvey();\n\n if (!visible || !title || !currentStep) {\n return null;\n }\n\n const showProgress = totalSteps > 1;\n\n return (\n <div className=\"sunboard-survey-overlay\">\n <section\n className=\"sunboard-survey-card\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={`${title} survey`}\n >\n <div className=\"sunboard-survey-head\">\n {subtitle ? <p className=\"sunboard-eyebrow\">{subtitle}</p> : null}\n <h2 className=\"sunboard-title\">{title}</h2>\n </div>\n\n {showProgress ? (\n <div className=\"sunboard-survey-progress-row\">\n <p className=\"sunboard-survey-stepcount\">\n Step {stepIndex + 1} of {totalSteps}\n </p>\n <div\n className=\"sunboard-progress\"\n role=\"progressbar\"\n aria-label={`${title} progress`}\n aria-valuemin={1}\n aria-valuemax={totalSteps}\n aria-valuenow={stepIndex + 1}\n >\n <div\n className=\"sunboard-progress-fill\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n ) : null}\n\n <form\n className=\"sunboard-survey-form\"\n onSubmit={(event) => {\n event.preventDefault();\n next();\n }}\n >\n {currentStep.title ? (\n <p className=\"sunboard-survey-step-title\">{currentStep.title}</p>\n ) : null}\n\n <ol className=\"sunboard-survey-questions\">\n {currentStep.questions.map((question) => (\n <li className=\"sunboard-survey-question\" key={question.key}>\n <p className=\"sunboard-survey-prompt\">\n {question.prompt}\n {question.required ? (\n <span aria-hidden=\"true\" className=\"sunboard-survey-required\">\n {\" *\"}\n </span>\n ) : null}\n </p>\n\n {question.type === \"text\" ? (\n <input\n className=\"sunboard-survey-input\"\n type=\"text\"\n value={\n typeof answers[question.key] === \"string\"\n ? (answers[question.key] as string)\n : \"\"\n }\n required={question.required}\n aria-label={question.prompt}\n onChange={(event) =>\n setAnswer(question.key, event.target.value)\n }\n />\n ) : (\n <div\n className=\"sunboard-survey-options\"\n role=\"group\"\n aria-label={question.prompt}\n >\n {question.options.map((option) => {\n const selected =\n question.type === \"multi-select\"\n ? Array.isArray(answers[question.key]) &&\n (answers[question.key] as string[]).includes(\n option.value\n )\n : answers[question.key] === option.value;\n\n return (\n <button\n className=\"sunboard-survey-option\"\n type=\"button\"\n key={option.value}\n aria-pressed={selected}\n data-selected={selected ? \"true\" : \"false\"}\n onClick={() =>\n question.type === \"multi-select\"\n ? toggleAnswer(question.key, option.value)\n : setAnswer(question.key, option.value)\n }\n >\n {option.label}\n </button>\n );\n })}\n </div>\n )}\n </li>\n ))}\n </ol>\n\n {error ? (\n <p className=\"sunboard-survey-error\" role=\"alert\">\n {error}\n </p>\n ) : null}\n\n <div className=\"sunboard-survey-nav\">\n {!isFirstStep ? (\n <button\n className=\"sunboard-survey-back\"\n type=\"button\"\n onClick={back}\n >\n Back\n </button>\n ) : (\n <span />\n )}\n <button\n className=\"sunboard-survey-submit\"\n type=\"submit\"\n disabled={isLastStep ? !canSubmit : !canAdvance}\n >\n {isLastStep ? (submitting ? \"Submitting…\" : \"Finish\") : \"Continue\"}\n </button>\n </div>\n </form>\n </section>\n </div>\n );\n}\n\nexport const Sunboard = Object.assign(SunboardChecklist, {\n Checklist: SunboardChecklist,\n Survey: SunboardSurvey,\n Hotspots: SunboardHotspots,\n Tooltips: SunboardTooltips,\n Tour: SunboardTour\n});\n","import { useCallback, useMemo } from \"react\";\nimport type { SunboardPlacement } from \"@sunboard/core\";\nimport { interpolateTokens } from \"../lib/interpolate-tokens\";\nimport { useSunboard } from \"./useSunboard\";\n\nexport type TooltipView = {\n key: string;\n title: string;\n body?: string;\n /** CSS selector of the element the tooltip points at. */\n target: string;\n placement: SunboardPlacement;\n /** True when a tour action is attached; `engage` starts it. */\n hasAction: boolean;\n actionLabel?: string;\n /** True when the user asked for this tooltip via a checklist step. */\n requested: boolean;\n};\n\nexport type UseTooltipsResult = {\n /**\n * Tooltips that qualify for the current route (undismissed, or explicitly\n * requested). Render ONE at a time — the first whose target you can anchor.\n */\n tooltips: TooltipView[];\n /** Report that a tooltip is actually on screen (deduped per session). */\n markSeen: (key: string) => void;\n /** Close without acting — the tooltip never shows again. */\n dismiss: (key: string) => void;\n /** Take the tooltip's action: starts its tour and retires the tooltip. */\n engage: (key: string) => void;\n /**\n * Report that the user clicked the tooltip's target element: the nudge\n * worked, so this retires the tooltip and records the engagement.\n */\n markTargetClicked: (key: string) => void;\n};\n\n/**\n * Headless counterpart of `<SunboardTooltips>`: the same visibility, seen,\n * dismissal, and engagement semantics with rendering left to the host.\n */\nexport function useTooltips(): UseTooltipsResult {\n const {\n user,\n visibleTooltips,\n markTooltipSeen,\n dismissTooltip,\n startTour\n } = useSunboard();\n\n const tooltips = useMemo<TooltipView[]>(\n () =>\n visibleTooltips.map(({ key, tooltip, requested }) => ({\n key,\n title: interpolateTokens(tooltip.title, user ?? null),\n body: tooltip.body\n ? interpolateTokens(tooltip.body, user ?? null)\n : undefined,\n target: tooltip.target,\n placement: tooltip.placement,\n hasAction: Boolean(tooltip.action),\n actionLabel: tooltip.actionLabel\n ? interpolateTokens(tooltip.actionLabel, user ?? null)\n : undefined,\n requested\n })),\n [user, visibleTooltips]\n );\n\n const engage = useCallback(\n (key: string) => {\n const entry = visibleTooltips.find((candidate) => candidate.key === key);\n\n void dismissTooltip(key, { via: \"action\" });\n\n if (entry?.tooltip.action) {\n void startTour(entry.tooltip.action.tour);\n }\n },\n [dismissTooltip, startTour, visibleTooltips]\n );\n\n return {\n tooltips,\n markSeen: markTooltipSeen,\n dismiss: (key) => {\n void dismissTooltip(key);\n },\n engage,\n markTargetClicked: (key) => {\n void dismissTooltip(key, { via: \"target\" });\n }\n };\n}\n"],"mappings":"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"}
package/dist/styles.css CHANGED
@@ -235,27 +235,35 @@
235
235
  grid-template-columns: 20px minmax(0, 1fr);
236
236
  gap: 12px;
237
237
  align-items: start;
238
- border: 0;
239
- background: transparent;
240
- color: inherit;
241
- cursor: default;
242
- font: inherit;
243
- text-align: left;
244
238
  padding: 10px 8px;
245
239
  margin: 0 -8px;
246
240
  border-radius: 8px;
247
241
  }
248
242
 
249
- button.sunboard-step-row {
250
- grid-template-columns: 20px minmax(0, 1fr) 16px;
251
- cursor: pointer;
243
+ /* Hovering anywhere on a row with something to click (an action, a checkable
244
+ checkbox, or both) highlights the whole row, like the old row-button did. */
245
+ .sunboard-step-row[data-interactive="true"]:hover {
246
+ background: var(--sunboard-checklist-muted);
252
247
  }
253
248
 
254
- button.sunboard-step-row:hover {
255
- background: var(--sunboard-checklist-muted);
249
+ .sunboard-step-main {
250
+ display: grid;
251
+ width: 100%;
252
+ grid-template-columns: minmax(0, 1fr) 16px;
253
+ gap: 12px;
254
+ align-items: start;
255
+ border: 0;
256
+ background: transparent;
257
+ color: inherit;
258
+ cursor: pointer;
259
+ font: inherit;
260
+ text-align: left;
261
+ padding: 0;
262
+ margin: 0;
263
+ border-radius: 4px;
256
264
  }
257
265
 
258
- button.sunboard-step-row:focus-visible {
266
+ .sunboard-step-main:focus-visible {
259
267
  outline: 2px solid var(--sunboard-checklist-primary);
260
268
  outline-offset: 2px;
261
269
  }
@@ -267,6 +275,8 @@ button.sunboard-step-row:focus-visible {
267
275
  place-items: center;
268
276
  border: 2px solid var(--sunboard-checklist-border);
269
277
  border-radius: 6px;
278
+ background: transparent;
279
+ padding: 0;
270
280
  color: var(--sunboard-checklist-primary-foreground);
271
281
  }
272
282
 
@@ -276,6 +286,38 @@ button.sunboard-step-row:focus-visible {
276
286
  stroke-width: 3;
277
287
  }
278
288
 
289
+ /* Checkable: the status is a button; preview the check on hover/focus. */
290
+ button.sunboard-step-status {
291
+ cursor: pointer;
292
+ }
293
+
294
+ button.sunboard-step-status svg {
295
+ opacity: 0;
296
+ color: var(--sunboard-checklist-muted-foreground);
297
+ transition: opacity 120ms ease;
298
+ }
299
+
300
+ button.sunboard-step-status:hover,
301
+ button.sunboard-step-status:focus-visible {
302
+ border-color: var(--sunboard-checklist-success);
303
+ }
304
+
305
+ button.sunboard-step-status:hover svg,
306
+ button.sunboard-step-status:focus-visible svg {
307
+ opacity: 1;
308
+ }
309
+
310
+ button.sunboard-step-status:focus-visible {
311
+ outline: 2px solid var(--sunboard-checklist-primary);
312
+ outline-offset: 2px;
313
+ }
314
+
315
+ /* manual: false and not yet complete: the checkbox is inert and says so. */
316
+ .sunboard-step-status[data-locked="true"] {
317
+ border-style: dashed;
318
+ opacity: 0.55;
319
+ }
320
+
279
321
  .sunboard-step[data-completed="true"] .sunboard-step-status {
280
322
  border-color: var(--sunboard-checklist-success);
281
323
  background: var(--sunboard-checklist-success);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sunboard/react",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "React SDK for the Sunboard onboarding runtime.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@floating-ui/react": "^0.27.19",
29
- "@sunboard/core": "0.8.1"
29
+ "@sunboard/core": "0.10.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "react": ">=18.3.0 || >=19.0.0",