onboardme-sdk 0.0.1

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.
Files changed (69) hide show
  1. package/ARCHITECTURE-v2.md +225 -0
  2. package/dist/sdk.iife.js +348 -0
  3. package/package.json +22 -0
  4. package/src/__tests__/day1.test.ts +37 -0
  5. package/src/__tests__/day2.test.ts +447 -0
  6. package/src/__tests__/day3.test.ts +110 -0
  7. package/src/__tests__/day4.test.ts +115 -0
  8. package/src/__tests__/day5.test.ts +102 -0
  9. package/src/__tests__/snapshot-dom-collector.test.ts +153 -0
  10. package/src/__tests__/snapshot-sender.test.ts +111 -0
  11. package/src/__tests__/v2-integration.test.ts +305 -0
  12. package/src/__tests__/v2-positioner.test.ts +115 -0
  13. package/src/__tests__/v2-renderer.test.ts +189 -0
  14. package/src/__tests__/v2-types.test.ts +74 -0
  15. package/src/__tests__/week2-day1.test.ts +62 -0
  16. package/src/__tests__/week2-day2.test.ts +128 -0
  17. package/src/__tests__/week2-day3.test.ts +128 -0
  18. package/src/__tests__/week2-day4.test.ts +177 -0
  19. package/src/__tests__/week2-day5.test.ts +294 -0
  20. package/src/__tests__/week3-day1.test.ts +169 -0
  21. package/src/__tests__/week3-day2.test.ts +267 -0
  22. package/src/__tests__/week3-day3.test.ts +213 -0
  23. package/src/__tests__/week3-day4.test.ts +213 -0
  24. package/src/__tests__/week3-day5.test.ts +350 -0
  25. package/src/__tests__/week4-day1.test.ts +277 -0
  26. package/src/__tests__/week4-day2.test.ts +227 -0
  27. package/src/__tests__/week4-day3.test.ts +323 -0
  28. package/src/__tests__/week4-day4.test.ts +210 -0
  29. package/src/__tests__/week4-day5.test.ts +503 -0
  30. package/src/__tests__/week5-day1.test.ts +152 -0
  31. package/src/__tests__/week5-day2.test.ts +222 -0
  32. package/src/__tests__/week5-day3.test.ts +297 -0
  33. package/src/__tests__/week5-day4.test.ts +306 -0
  34. package/src/__tests__/week5-day5.test.ts +345 -0
  35. package/src/__tests__/week7-day5-api-flows.test.ts +353 -0
  36. package/src/auto-generate/context-collector.ts +47 -0
  37. package/src/auto-generate/flow-generator-client.ts +97 -0
  38. package/src/browser.ts +5 -0
  39. package/src/components/celebration.ts +44 -0
  40. package/src/components/checklist-css.ts +159 -0
  41. package/src/components/checklist.ts +295 -0
  42. package/src/components/modal-css.ts +96 -0
  43. package/src/components/modal.ts +171 -0
  44. package/src/components/shadow-host.ts +30 -0
  45. package/src/core/api-client.ts +39 -0
  46. package/src/core/api-flows.ts +204 -0
  47. package/src/core/config.ts +37 -0
  48. package/src/core/event-batcher.ts +169 -0
  49. package/src/core/sdk.ts +301 -0
  50. package/src/detection/user-detection.ts +55 -0
  51. package/src/index.ts +95 -0
  52. package/src/snapshot/dom-collector.ts +193 -0
  53. package/src/snapshot/sender.ts +105 -0
  54. package/src/storage/event-listener.ts +59 -0
  55. package/src/storage/progress-tracker.ts +78 -0
  56. package/src/styles/checklist-css.ts +159 -0
  57. package/src/styles/checklist.css +166 -0
  58. package/src/styles/modal-css.ts +96 -0
  59. package/src/styles/modal.css +102 -0
  60. package/src/utils/dom.ts +49 -0
  61. package/src/utils/fingerprint.ts +20 -0
  62. package/src/utils/logger.ts +17 -0
  63. package/src/v2/positioner.ts +105 -0
  64. package/src/v2/renderer.ts +287 -0
  65. package/src/v2/styles.ts +89 -0
  66. package/src/v2/types.ts +53 -0
  67. package/tsconfig.json +11 -0
  68. package/vite.config.ts +28 -0
  69. package/vitest.config.ts +7 -0
@@ -0,0 +1,225 @@
1
+ # SDK Architecture — v2 (Phase F)
2
+
3
+ This document describes the OnboardMe SDK's v2 architecture, introduced in the Phase A–F rewrite (May 2026). The SDK now acts as a **thin renderer** for server-driven flows, partitioning between v1 (legacy) and v2 (new) rendering paths.
4
+
5
+ ## Overview
6
+
7
+ The SDK's job is simple: **fetch flows from the API and render them**. All flow logic, targeting, and publishing decisions happen server-side (Phases A–E). The SDK just receives the final, validated flow and displays it.
8
+
9
+ **Key points:**
10
+ - SDK initialization is synchronous and lightweight
11
+ - v2 flows are fetched via polling (every 60 seconds)
12
+ - Each step type (tooltip, modal, highlight) is rendered into the Shadow DOM
13
+ - User actions (next, skip, complete) trigger step transitions or flow teardown
14
+ - Bundle size: 9.82 KB gzipped (well under 50 KB limit)
15
+
16
+ ## Initialization Flow
17
+
18
+ ```
19
+ OnboardMe.init(config)
20
+
21
+ validateConfig()
22
+
23
+ getAnonymousId() → generate or retrieve existing ID
24
+
25
+ detectUser() → is this a new user?
26
+
27
+ configureBatcher() → wire event streaming
28
+
29
+ fetchFlowsWithFallback() [async] → GET /v1/flows with checksumHash
30
+
31
+ _renderFlows() → partition & render (v2 or legacy)
32
+
33
+ startFlowsPolling() [async] → poll every 60s, re-render on hash change
34
+ ```
35
+
36
+ All async work is deferred via `setTimeout(0)` so `init()` returns immediately and doesn't block the host app's first paint.
37
+
38
+ ## v2 vs Legacy Flow Partition
39
+
40
+ The SDK supports both v1 (legacy) and v2 flows. The `_renderFlows()` function in `src/core/sdk.ts` routes each flow to the correct renderer:
41
+
42
+ ```typescript
43
+ for (const flow of flows) {
44
+ if (isV2FlowConfig(flow.config ?? flow)) {
45
+ // v2 schema: { steps: [ { type: 'tooltip' | 'modal' | 'highlight', ... } ] }
46
+ renderV2Flow(config, shadowRoot) // → src/v2/renderer.ts
47
+ } else {
48
+ // v1 schema: { steps: [ { type: 'modal' | 'checklist', ... } ] }
49
+ showModal(step, shadowRoot) // → src/components/modal.ts
50
+ renderChecklist(flow, shadowRoot) // → src/components/checklist.ts
51
+ }
52
+ }
53
+ ```
54
+
55
+ Detection is via `isV2FlowConfig()`, which checks if the flow has the v2 structure.
56
+
57
+ ## v2 Renderer (`src/v2/renderer.ts`)
58
+
59
+ The v2 renderer walks the steps array and mounts one DOM element per step.
60
+
61
+ ### Step Types
62
+
63
+ | Type | Rendering | User Interaction |
64
+ |------|-----------|------------------|
65
+ | **tooltip** | Card anchored to a target element via selector + placement | Click action button → advance |
66
+ | **modal** | Centered card with semi-transparent backdrop | Click action button → advance |
67
+ | **highlight** | Outline ring around a target element, no card | Click action button → advance |
68
+
69
+ ### Rendering Loop
70
+
71
+ ```
72
+ renderCurrentStep(index)
73
+
74
+ switch (step.type)
75
+ → tooltip: mountTooltip(ctx, step)
76
+ → modal: mountModal(ctx, step)
77
+ → other: mountHighlight(ctx, step)
78
+
79
+ Wire action button → advance(ctx, step)
80
+ → action.type = 'next' → ctx.index++, render next step
81
+ → action.type = 'skip' → teardown (dismiss whole flow)
82
+ → action.type = 'complete' → teardown (flow finished)
83
+ ```
84
+
85
+ ### DOM Structure
86
+
87
+ All steps render into a shared Shadow Root. When advancing to the next step, the previous step's DOM is cleaned up before the new one is mounted — no visual overlap.
88
+
89
+ ```html
90
+ <shadow-root>
91
+ <style>v2-styles</style>
92
+ <div class="om2-tooltip">
93
+ <h2>Tooltip title</h2>
94
+ <p>Tooltip content</p>
95
+ <button>Next</button>
96
+ </div>
97
+ </shadow-root>
98
+ ```
99
+
100
+ ### Positioning
101
+
102
+ - **Tooltip placement:** Top, bottom, left, right relative to target element. Computed by `positioner.ts` to avoid viewport overflow.
103
+ - **Modal placement:** Always centered on screen (no positioning needed).
104
+ - **Highlight placement:** Outline computed from target element's bounding rect.
105
+
106
+ ## Files & Responsibilities
107
+
108
+ | File | Responsibility |
109
+ |------|-----------------|
110
+ | `src/index.ts` | Public SDK API (`OnboardMe.init`, `.identify`, `.track`) |
111
+ | `src/core/sdk.ts` | Core orchestration (init, flow polling, v2/legacy partition) |
112
+ | `src/core/event-batcher.ts` | Collect events, batch, send to API (fire-and-forget) |
113
+ | `src/v2/renderer.ts` | Walk steps, mount/teardown DOM per step, wire actions |
114
+ | `src/v2/positioner.ts` | Compute tooltip position relative to target |
115
+ | `src/v2/types.ts` | TypeScript types for v2 flow schema |
116
+ | `src/v2/styles.ts` | Injected CSS for tooltip/modal/highlight |
117
+ | `src/components/shadow-host.ts` | Create/retrieve shadow root (shared by v1 and v2) |
118
+ | `src/components/modal.ts` | Legacy v1: welcome modal component |
119
+ | `src/components/checklist.ts` | Legacy v1: progress checklist component |
120
+ | `src/components/celebration.ts` | Legacy v1: completion banner |
121
+
122
+ ## Data Flow
123
+
124
+ ### Polling & Updates
125
+
126
+ ```
127
+ every 60 seconds:
128
+ fetchFlows(GET /v1/flows)
129
+
130
+ compute checksumHash = SHA256(flows).slice(0, 8)
131
+
132
+ if (checksumHash changed):
133
+ teardownV2Renders() // destroy old renders
134
+ _renderFlows(newFlows) // mount new renders
135
+ else:
136
+ (no-op, skip re-render)
137
+ ```
138
+
139
+ The SDK only re-renders when the flow data actually changed. This avoids unnecessary DOM operations and keeps re-renders silent to the user.
140
+
141
+ ### Event Tracking
142
+
143
+ ```
144
+ user clicks "Next" button on tooltip
145
+
146
+ pushEvent('step_action_taken', { eventName: '...', ... })
147
+
148
+ event-batcher collects, debounces, batches
149
+
150
+ POST /v1/events with batch every 5 seconds (or earlier if queue fills)
151
+
152
+ API writes to analytics DB (ClickHouse)
153
+ ```
154
+
155
+ Events are fire-and-forget — the user sees the step advance immediately, regardless of whether the API call succeeds.
156
+
157
+ ## Extending for New Step Types
158
+
159
+ To add a new step type (e.g., `sidebar`), follow this checklist:
160
+
161
+ 1. Add the type to `src/v2/types.ts`:
162
+ ```typescript
163
+ export type V2StepType = 'tooltip' | 'modal' | 'highlight' | 'sidebar'
164
+ ```
165
+
166
+ 2. Add rendering logic to `src/v2/renderer.ts`:
167
+ ```typescript
168
+ } else if (step.type === 'sidebar') {
169
+ mountSidebar(ctx, step)
170
+ }
171
+
172
+ function mountSidebar(ctx: RenderContext, step: V2Step): void {
173
+ // Mount sidebar DOM, wire action button, etc.
174
+ }
175
+ ```
176
+
177
+ 3. Update `src/v2/styles.ts` with CSS for the new component.
178
+
179
+ 4. Test via `src/__tests__/v2-renderer.test.ts`.
180
+
181
+ 5. Verify bundle size is still <50 KB: `pnpm --filter @onboardme/sdk build`
182
+
183
+ ## Testing
184
+
185
+ ### Unit Tests
186
+
187
+ Run `pnpm --filter @onboardme/sdk test` to execute:
188
+ - `v2-renderer.test.ts` — step rendering, DOM cleanup, action wiring
189
+ - `v2-positioner.test.ts` — tooltip positioning edge cases
190
+ - `v2-types.test.ts` — flow schema validation
191
+
192
+ ### Manual Testing
193
+
194
+ Open `scripts/test-v2-rendering.html` in a browser to verify:
195
+ 1. DOM elements render correctly
196
+ 2. Step positioning is correct (tooltips don't clip, highlights are visible)
197
+ 3. Action buttons advance the flow
198
+ 4. Cleanup happens on complete/skip
199
+
200
+ ### Integration with API
201
+
202
+ Phase E (PM dashboard) creates v2 flows and publishes them. The SDK fetches and renders them within seconds. End-to-end flow: code snapshot → AI generation → PM publish → SDK fetch & render.
203
+
204
+ ## Performance
205
+
206
+ - **Init:** <50ms (no network calls)
207
+ - **First fetch (async):** ~200–500ms (network latency)
208
+ - **Re-render on flow change:** <20ms (DOM operations)
209
+ - **Bundle size:** 9.82 KB gzipped (target: <15 KB, limit: <50 KB)
210
+
211
+ ## Future Enhancements
212
+
213
+ These are deferred beyond Phase F:
214
+
215
+ - **User targeting:** Show flows only to users matching certain segments/traits
216
+ - **Completion tracking:** Track which step each user is on, compute funnel drop-off
217
+ - **Step branching:** Different flows based on user properties
218
+ - **Analytics dashboards:** Visualize drop-off, completion rates, dwell time per step
219
+
220
+ All of these can layer on top of the current v2 foundation without architectural changes.
221
+
222
+ ---
223
+
224
+ **Version:** Phase F (May 2026)
225
+ **Status:** Production-ready for v2 flows; legacy v1 flows still supported.
@@ -0,0 +1,348 @@
1
+ var OnboardMe=function(){"use strict";let M=!1;function Te(e){M=e}const c={log(e,...t){M&&console.log(`[OnboardMe] ${e}`,...t)},warn(e,...t){M&&console.warn(`[OnboardMe] ${e}`,...t)},error(e,...t){M&&console.error(`[OnboardMe] ${e}`,...t)}};function $e(e){if(!e||typeof e!="object")return c.warn("init() called with no config — OnboardMe will not run."),null;const t=e;return!t.productId||typeof t.productId!="string"?(c.warn("config.productId is required and must be a string — OnboardMe will not run."),null):(t.flows!==void 0&&!Array.isArray(t.flows)&&(c.warn("config.flows must be an array — defaulting to []."),t.flows=[]),{productId:t.productId,eventsEndpoint:t.eventsEndpoint,autoGenerate:t.autoGenerate??void 0,apiFlows:t.apiFlows??void 0,flows:t.flows??[],user:t.user??void 0,debug:typeof t.debug=="boolean"?t.debug:!1})}const Ie=48*60*60*1e3;function Ne(e){var o;const t=`onboardme_seen_${e.productId}`;try{if(localStorage.getItem(t)!==null)return{isNew:!1,reason:"returning"}}catch{}const n=(o=e.user)==null?void 0:o.createdAt;return n?Date.now()-new Date(n).getTime()>=Ie?{isNew:!1,reason:"returning"}:(Q(t),{isNew:!0,reason:"new_account"}):(Q(t),{isNew:!0,reason:"first_visit"})}function Q(e){try{localStorage.setItem(e,"1")}catch{}}function Me(e){const t=`onboardme_anon_${e}`;try{const n=localStorage.getItem(t);if(n)return n;const o=crypto.randomUUID();return localStorage.setItem(t,o),o}catch{return crypto.randomUUID()}}const Z="onboardme-root";function O(){const e=document.getElementById(Z);if(e!=null&&e.shadowRoot)return e.shadowRoot;const t=document.createElement("div");t.id=Z,document.body.appendChild(t);const n=t.attachShadow({mode:"open"}),o=document.createElement("style");return o.dataset.onboardme="styles",n.appendChild(o),n}const Oe=`
2
+ .om-overlay {
3
+ position: fixed;
4
+ inset: 0;
5
+ background: rgba(0, 0, 0, 0.45);
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ z-index: 2147483647;
10
+ }
11
+
12
+ .om-modal {
13
+ background: #ffffff;
14
+ border-radius: 12px;
15
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
16
+ max-width: 480px;
17
+ width: calc(100% - 32px);
18
+ padding: 32px;
19
+ box-sizing: border-box;
20
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
+ color: #111827;
22
+ }
23
+
24
+ .om-modal__title {
25
+ margin: 0 0 12px;
26
+ font-size: 20px;
27
+ font-weight: 700;
28
+ line-height: 1.3;
29
+ color: #111827;
30
+ }
31
+
32
+ .om-modal__body {
33
+ margin: 0 0 24px;
34
+ font-size: 15px;
35
+ line-height: 1.6;
36
+ color: #4b5563;
37
+ }
38
+
39
+ .om-modal__actions {
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: 10px;
43
+ align-items: stretch;
44
+ }
45
+
46
+ .om-btn-primary {
47
+ display: block;
48
+ width: 100%;
49
+ padding: 12px 20px;
50
+ background: #4f46e5;
51
+ color: #ffffff;
52
+ font-size: 15px;
53
+ font-weight: 600;
54
+ border: none;
55
+ border-radius: 8px;
56
+ cursor: pointer;
57
+ text-align: center;
58
+ transition: background 0.15s ease;
59
+ }
60
+
61
+ .om-btn-primary:hover {
62
+ background: #4338ca;
63
+ }
64
+
65
+ .om-btn-primary:focus-visible {
66
+ outline: 3px solid #818cf8;
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ .om-btn-skip {
71
+ display: block;
72
+ background: none;
73
+ border: none;
74
+ padding: 6px 0;
75
+ color: #6b7280;
76
+ font-size: 14px;
77
+ cursor: pointer;
78
+ text-align: center;
79
+ text-decoration: underline;
80
+ transition: color 0.15s ease;
81
+ }
82
+
83
+ .om-btn-skip:hover {
84
+ color: #374151;
85
+ }
86
+
87
+ .om-btn-skip:focus-visible {
88
+ outline: 2px solid #818cf8;
89
+ outline-offset: 2px;
90
+ border-radius: 4px;
91
+ }
92
+ `,He=1e4;async function Le(e,t){const n=new AbortController,o=setTimeout(()=>n.abort(),He);try{const s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:t}),signal:n.signal});return s.ok?!0:(c.warn(`event flush failed — server responded ${s.status}`),!1)}catch(s){return c.warn(`event flush failed — ${s.message}`),!1}finally{clearTimeout(o)}}const qe=100,Fe=5e3;let E="",ee="",P,b=[],_=null,te=!1,ne=null,oe=null;function Ue(e,t){E=e,ee=t}function ze(e){P=e}function y(e,t={}){const n={eventId:crypto.randomUUID(),anonymousId:ee,...P!==void 0?{userId:P}:{},eventType:e,pageUrl:typeof window<"u"?window.location.href:"",timestamp:Date.now(),...t};b.length>=qe&&(b.shift(),c.warn("event queue full — oldest event dropped")),b.push(n),c.log(`event queued: ${e} (queue size: ${b.length})`),b.length===1&&_===null&&(_=setTimeout(()=>{_=null,se().catch(()=>{})},Fe))}async function se(){if(b.length===0)return;if(!E){c.warn("flushEvents called before configureBatcher — events not sent");return}_!==null&&(clearTimeout(_),_=null);const e=[...b];await Le(E,e)&&(b=[],c.log(`flushed ${e.length} event(s)`))}function De(){te||(te=!0,ne=()=>{document.visibilityState==="hidden"&&se().catch(()=>{})},oe=()=>{if(b.length===0||!E)return;const e=JSON.stringify({events:b});navigator.sendBeacon(E,new Blob([e],{type:"application/json"}))&&(b=[],_!==null&&(clearTimeout(_),_=null))},document.addEventListener("visibilitychange",ne),window.addEventListener("beforeunload",oe))}const j=new WeakMap;function ie(e){return`onboardme_modal_shown_${e}`}function Pe(e){const t=e.querySelector('style[data-onboardme="styles"]');t&&!t.textContent&&(t.textContent=Oe)}function je(e,t){Pe(t);const n=document.createElement("div");n.className="om-overlay",n.dataset.onboardme="overlay";const o=document.createElement("div");o.className="om-modal",o.setAttribute("role","dialog"),o.setAttribute("aria-modal","true"),o.setAttribute("aria-labelledby","om-modal-title");const s=document.createElement("h2");s.id="om-modal-title",s.className="om-modal__title",s.textContent=e.title;const i=document.createElement("p");i.className="om-modal__body",i.textContent=e.body;const r=document.createElement("div");r.className="om-modal__actions";const a=document.createElement("button");if(a.className="om-btn-primary",a.type="button",a.textContent=e.primaryCta??"Get started",a.dataset.onboardme="primary-cta",r.appendChild(a),e.dismissible!==!1){const l=document.createElement("button");l.className="om-btn-skip",l.type="button",l.textContent=e.secondaryCta??"Skip for now",l.dataset.onboardme="skip-cta",r.appendChild(l)}o.appendChild(s),o.appendChild(i),o.appendChild(r),n.appendChild(o),t.appendChild(n)}function B(e){const t=e.querySelector('[data-onboardme="overlay"]');t&&e.removeChild(t);const n=j.get(e);n&&(document.removeEventListener("keydown",n),j.delete(e))}function Be(e,t,n,o){if(sessionStorage.getItem(ie(n))!==null)return;sessionStorage.setItem(ie(n),"1"),y("flow_started",{flowId:o}),y("step_viewed",{flowId:o,stepId:e.id,stepIndex:0}),je(e,t);const s=t.querySelector('[data-onboardme="primary-cta"]');s==null||s.addEventListener("click",()=>{y("step_completed",{flowId:o,stepId:e.id}),B(t)});const i=t.querySelector('[data-onboardme="skip-cta"]');i==null||i.addEventListener("click",()=>{y("step_skipped",{flowId:o,stepId:e.id}),B(t)});const r=a=>{a.key==="Escape"&&(y("step_skipped",{flowId:o,stepId:e.id}),B(t))};j.set(t,r),document.addEventListener("keydown",r),s==null||s.focus()}const re={completedSteps:[],lastUpdated:0};function le(e,t){return`onboardme_progress_${e}_${t}`}function ae(e,t){try{const n=localStorage.getItem(le(e,t));return n?JSON.parse(n):{...re,completedSteps:[]}}catch{return{...re,completedSteps:[]}}}function Ge(e,t,n){const o={completedSteps:n,lastUpdated:Date.now()};localStorage.setItem(le(e,t),JSON.stringify(o))}function ce(e,t,n){const o=ae(e,t);o.completedSteps.includes(n)||Ge(e,t,[...o.completedSteps,n])}function Re(e,t){const n=e.steps.find(r=>r.type==="checklist"),o=(n==null?void 0:n.items)??[],s=new Map;for(const r of o)r.completionEvent&&s.set(r.completionEvent,r.id);const i=x.track;return x.track=(r,a)=>{i.call(x,r,a);const l=s.get(r);l!==void 0&&t(l)},()=>{x.track=i,s.clear()}}const Xe=`
93
+ .om-checklist {
94
+ position: fixed;
95
+ bottom: 24px;
96
+ right: 24px;
97
+ width: 320px;
98
+ background: #ffffff;
99
+ border-radius: 12px;
100
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
101
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
102
+ font-size: 14px;
103
+ color: #1a1a2e;
104
+ z-index: 9999;
105
+ overflow: hidden;
106
+ transition: width 0.2s ease, height 0.2s ease;
107
+ }
108
+
109
+ .om-checklist__header {
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: space-between;
113
+ padding: 14px 16px 10px;
114
+ border-bottom: 1px solid #f0f0f0;
115
+ }
116
+
117
+ .om-checklist__title {
118
+ font-weight: 600;
119
+ font-size: 14px;
120
+ color: #1a1a2e;
121
+ }
122
+
123
+ .om-checklist__collapse-btn {
124
+ background: none;
125
+ border: none;
126
+ cursor: pointer;
127
+ font-size: 18px;
128
+ color: #888;
129
+ padding: 0 2px;
130
+ line-height: 1;
131
+ transition: color 0.15s;
132
+ }
133
+
134
+ .om-checklist__collapse-btn:hover {
135
+ color: #1a1a2e;
136
+ }
137
+
138
+ .om-checklist__progress {
139
+ padding: 10px 16px 6px;
140
+ }
141
+
142
+ .om-checklist__progress-bar {
143
+ width: 100%;
144
+ height: 6px;
145
+ background: #ebebeb;
146
+ border-radius: 3px;
147
+ overflow: hidden;
148
+ margin-bottom: 6px;
149
+ }
150
+
151
+ .om-checklist__progress-bar-fill {
152
+ height: 100%;
153
+ background: #4f46e5;
154
+ border-radius: 3px;
155
+ transition: width 0.35s ease;
156
+ }
157
+
158
+ .om-checklist__progress-label {
159
+ font-size: 12px;
160
+ color: #888;
161
+ }
162
+
163
+ .om-checklist__items {
164
+ list-style: none;
165
+ margin: 0;
166
+ padding: 6px 0 10px;
167
+ }
168
+
169
+ .om-checklist__item {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 10px;
173
+ padding: 8px 16px;
174
+ cursor: pointer;
175
+ transition: background 0.1s;
176
+ }
177
+
178
+ .om-checklist__item:hover {
179
+ background: #f8f8fb;
180
+ }
181
+
182
+ .om-checklist__item-check {
183
+ flex-shrink: 0;
184
+ width: 18px;
185
+ height: 18px;
186
+ border: 2px solid #d1d5db;
187
+ border-radius: 50%;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ font-size: 10px;
192
+ color: transparent;
193
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
194
+ }
195
+
196
+ .om-checklist__item-label {
197
+ flex: 1;
198
+ font-size: 13px;
199
+ color: #1a1a2e;
200
+ }
201
+
202
+ .om-checklist__item--done .om-checklist__item-check {
203
+ background: #4f46e5;
204
+ border-color: #4f46e5;
205
+ color: #ffffff;
206
+ }
207
+
208
+ .om-checklist__item--done .om-checklist__item-label {
209
+ text-decoration: line-through;
210
+ color: #aaa;
211
+ }
212
+
213
+ .om-checklist__item--optional .om-checklist__item-label {
214
+ color: #888;
215
+ }
216
+
217
+ .om-badge {
218
+ flex-shrink: 0;
219
+ background: #f3f4f6;
220
+ color: #6b7280;
221
+ font-size: 11px;
222
+ font-weight: 500;
223
+ padding: 2px 7px;
224
+ border-radius: 10px;
225
+ }
226
+
227
+ .om-checklist--collapsed {
228
+ width: auto;
229
+ height: auto;
230
+ }
231
+
232
+ .om-checklist--collapsed .om-checklist__progress,
233
+ .om-checklist--collapsed .om-checklist__items {
234
+ display: none;
235
+ }
236
+
237
+ .om-checklist--collapsed .om-checklist__header {
238
+ border-bottom: none;
239
+ padding: 10px 14px;
240
+ cursor: pointer;
241
+ }
242
+
243
+ .om-checklist--collapsed .om-checklist__collapse-btn {
244
+ display: none;
245
+ }
246
+ `,G=new WeakMap;function We(e){var n;const t=e.querySelector('style[data-onboardme="styles"]');t&&((n=t.textContent)!=null&&n.includes(".om-checklist")||(t.textContent=(t.textContent??"")+Xe))}function Ye(e){const t=e.steps.find(n=>n.type==="checklist");return(t==null?void 0:t.items)??[]}function de(e){return e.filter(t=>t.required!==!1).length}function Je(e,t){const n=document.createElement("li"),o=["om-checklist__item"];t&&o.push("om-checklist__item--done"),e.required===!1&&o.push("om-checklist__item--optional"),n.className=o.join(" "),n.dataset.itemId=e.id;const s=document.createElement("span");s.className="om-checklist__item-check",s.setAttribute("aria-hidden","true"),s.textContent=t?"✓":"";const i=document.createElement("span");if(i.className="om-checklist__item-label",i.textContent=e.label,n.appendChild(s),n.appendChild(i),e.required===!1){const r=document.createElement("span");r.className="om-badge",r.textContent="Optional",n.appendChild(r)}return n}function ue(e,t,n){const o=de(t),s=t.filter(w=>w.required!==!1&&n.includes(w.id)).length,i=t.length,r=t.filter(w=>n.includes(w.id)).length,a=o>0?Math.round(s/o*100):0,l=e.querySelector(".om-checklist__progress-bar-fill");l&&(l.style.width=`${a}%`);const d=e.querySelector('[data-onboardme="progress-label"]');d&&(d.textContent=`${r} of ${i} complete`);const p=e.querySelector('[data-onboardme="collapsed-count"]');p&&(p.textContent=`${r} / ${i}`)}function Ke(e,t,n,o=!1,s="",i=""){var Ee,Ae;We(n),(Ee=G.get(n))==null||Ee(),G.delete(n);const r=n.querySelector('[data-onboardme="checklist"]');r&&n.removeChild(r);const a=Ye(e);o&&a.length>7&&console.warn("[OnboardMe] Checklist has more than 7 items — consider splitting the flow.");const l=[...a].sort((u,m)=>u.order-m.order),d=de(l),p=l.filter(u=>u.required!==!1&&t.completedSteps.includes(u.id)).length,w=l.length,T=l.filter(u=>t.completedSteps.includes(u.id)).length,Bt=d>0?Math.round(p/d*100):0,C=[...t.completedSteps],g=document.createElement("div");g.className="om-checklist",g.dataset.onboardme="checklist";const $=document.createElement("div");$.className="om-checklist__header";const I=document.createElement("span");I.className="om-checklist__title",I.textContent="Getting started";const v=document.createElement("span");v.className="om-checklist__collapsed-count",v.dataset.onboardme="collapsed-count",v.style.display="none",v.textContent=`${T} / ${w}`;const S=document.createElement("button");S.className="om-checklist__collapse-btn",S.type="button",S.dataset.onboardme="checklist-collapse",S.setAttribute("aria-label","Collapse checklist"),S.textContent="−",$.appendChild(I),$.appendChild(v),$.appendChild(S);const z=document.createElement("div");z.className="om-checklist__progress";const J=document.createElement("div");J.className="om-checklist__progress-bar";const K=document.createElement("div");K.className="om-checklist__progress-bar-fill",K.style.width=`${Bt}%`,J.appendChild(K);const D=document.createElement("span");D.className="om-checklist__progress-label",D.dataset.onboardme="progress-label",D.textContent=`${T} of ${w} complete`,z.appendChild(J),z.appendChild(D);const V=document.createElement("ul");V.className="om-checklist__items";for(const u of l){const m=t.completedSteps.includes(u.id);V.appendChild(Je(u,m))}g.appendChild($),g.appendChild(z),g.appendChild(V),n.appendChild(g);const Ce=((Ae=e.steps.find(u=>u.type==="checklist"))==null?void 0:Ae.id)??"";Ce&&i&&y("step_viewed",{flowId:i,stepId:Ce});const Gt=g.querySelectorAll(".om-checklist__item");for(const u of Gt)u.addEventListener("click",()=>{const m=u.dataset.itemId;if(!m||C.includes(m))return;s&&i&&(ce(s,i,m),y("checklist_item_done",{flowId:i,stepId:m})),C.push(m),u.classList.add("om-checklist__item--done");const N=u.querySelector(".om-checklist__item-check");N&&(N.textContent="✓"),ue(n,l,C)});S.addEventListener("click",u=>{u.stopPropagation(),g.classList.add("om-checklist--collapsed"),I.style.display="none",v.style.display="inline"}),g.addEventListener("click",()=>{g.classList.contains("om-checklist--collapsed")&&(g.classList.remove("om-checklist--collapsed"),I.style.display="",v.style.display="none")});const Rt=Re(e,u=>{if(C.includes(u))return;s&&i&&(ce(s,i,u),y("checklist_item_done",{flowId:i,stepId:u})),C.push(u);const m=g.querySelector(`[data-item-id="${u}"]`);if(m){m.classList.add("om-checklist__item--done");const N=m.querySelector(".om-checklist__item-check");N&&(N.textContent="✓")}ue(n,l,C)});G.set(n,Rt)}const Ve=`
247
+ .om-celebration {
248
+ position: fixed;
249
+ top: 1.25rem;
250
+ left: 50%;
251
+ transform: translateX(-50%);
252
+ background: #16a34a;
253
+ color: #ffffff;
254
+ font-family: system-ui, sans-serif;
255
+ font-size: 1rem;
256
+ font-weight: 600;
257
+ padding: 0.75rem 1.5rem;
258
+ border-radius: 0.5rem;
259
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
260
+ z-index: 999999;
261
+ text-align: center;
262
+ white-space: nowrap;
263
+ }
264
+ `;function Qe(e){if(!e.querySelector("#om-celebration-styles")){const n=document.createElement("style");n.id="om-celebration-styles",n.textContent=Ve,e.appendChild(n)}const t=document.createElement("div");t.className="om-celebration",t.setAttribute("role","status"),t.setAttribute("aria-live","polite"),t.textContent="🎉 You're all set!",e.appendChild(t),setTimeout(()=>{t.remove()},3e3)}const Ze=10,et=10,tt=15;function R(e,t){const n=new Set,o=[];for(const s of e){const i=(s.textContent??"").trim();if(i&&!n.has(i)&&(n.add(i),o.push(i),o.length>=t))break}return o}function nt(){const e=document.title??"",t=R(document.querySelectorAll("h1, h2, h3"),Ze),n=R(document.querySelectorAll("nav a"),et),o=R(document.querySelectorAll('button, [role="button"]'),tt);return{title:e,headings:t,navLinks:n,buttons:o}}const ot=7*24*60*60*1e3,st=1e4;function pe(e){return`onboardme_autogen_${e}`}function it(e,t){try{const n=localStorage.getItem(pe(e));if(!n)return null;const o=JSON.parse(n);return Date.now()-o.cachedAt>t?null:o.flows}catch{return null}}function rt(e,t){try{const n={flows:t,cachedAt:Date.now()};localStorage.setItem(pe(e),JSON.stringify(n))}catch{c.warn("auto-generate: localStorage quota exceeded — cache not saved")}}async function lt(e,t,n,o=ot){const s=it(t,o);if(s)return s;const i=new AbortController,r=setTimeout(()=>i.abort(),st);try{const a=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({productId:t,context:n}),signal:i.signal});if(!a.ok)return c.warn(`auto-generate: server responded ${a.status}`),[];const l=await a.json(),d=l!==null&&typeof l=="object"&&"flows"in l&&Array.isArray(l.flows)?l.flows:[];return rt(t,d),d}catch(a){return c.warn(`auto-generate: fetch failed — ${a.message}`),[]}finally{clearTimeout(r)}}function at(e,t){const n=new Set(e.map(s=>s.id)),o=t.filter(s=>!n.has(s.id));return[...e,...o]}let fe=null,H="";const ct=6e4,dt=36e5;async function X(e,t){try{const n=new AbortController,o=setTimeout(()=>n.abort(),1e4),s=await fetch(`${e}/v1/flows`,{method:"GET",headers:{"x-api-key":t,"Content-Type":"application/json"},signal:n.signal});return clearTimeout(o),s.ok?await s.json():(c.warn(`Failed to fetch flows: HTTP ${s.status}`),null)}catch(n){return n instanceof Error&&(n.name==="AbortError"?c.warn("Fetch flows timeout (10s)"):c.warn(`Fetch flows error: ${n.message}`)),null}}function ut(e){try{const t=localStorage.getItem(e);if(!t)return null;const n=JSON.parse(t);return Date.now()-n.timestamp>dt?(c.log("Flows cache expired (1h)"),localStorage.removeItem(e),null):n}catch{return c.warn("Failed to parse flows cache"),null}}function W(e,t,n){try{const o={flows:t,checksumHash:n,timestamp:Date.now()};localStorage.setItem(e,JSON.stringify(o))}catch{c.warn("Failed to save flows to cache")}}async function pt(e,t,n){const o=await X(e,t);if(o)return W(n,o.flows,o.checksumHash),o.flows,o.checksumHash,Date.now(),o;const s=ut(n);return s?(c.log("Using cached flows from localStorage"),{flows:s.flows,checksumHash:s.checksumHash}):(c.warn("No flows available (API down, cache empty)"),{flows:[],checksumHash:""})}function ft(e,t,n,o){fe||(c.log("Starting flows polling (60s interval)"),(async()=>{const s=await X(e,t);s&&s.checksumHash!==H&&(H=s.checksumHash,W(n,s.flows,s.checksumHash),o(s.flows,s.checksumHash))})(),fe=setInterval(async()=>{const s=await X(e,t);s&&s.checksumHash!==H&&(c.log("Flows updated (new checksum detected)"),H=s.checksumHash,W(n,s.flows,s.checksumHash),o(s.flows,s.checksumHash))},ct))}const me=500,he=200,mt=[{selector:"h1, h2, h3, h4, h5, h6",role:"heading"},{selector:'button, [role="button"]',role:"button"},{selector:"a[href]",role:"link"},{selector:"input, textarea, select",role:"input"},{selector:"form",role:"form"},{selector:'nav, [role="navigation"]',role:"nav"},{selector:"img[alt]",role:"image"}],ht={heading:[],button:["type","name","aria-label","data-testid"],link:["href","aria-label","data-testid"],input:["type","name","placeholder","aria-label","data-testid"],form:["name","action","method"],nav:["aria-label"],image:["alt","src"],other:[]};function gt(e=document,t=window){var s,i;const n=new Set,o=[];for(const{selector:r,role:a}of mt){const l=xt(e,r);for(const d of l){if(n.has(d)||!kt(d,t))continue;n.add(d);const p={selector:bt(d),role:a},w=wt(d);w&&(p.text=w);const T=_t(d,a);if(Object.keys(T).length>0&&(p.attributes=T),o.push(p),o.length>=me)break}if(o.length>=me)break}return{pageUrl:((s=t.location)==null?void 0:s.href)??"",pageTitle:e.title??"",collectedAt:Date.now(),elements:o,meta:{userAgent:(i=t.navigator)==null?void 0:i.userAgent,viewport:{width:t.innerWidth??0,height:t.innerHeight??0}}}}function bt(e){const t=e.getAttribute("data-testid");if(t)return`[data-testid="${ge(t)}"]`;const n=e.getAttribute("id");if(n&&!yt(n))return`#${ge(n)}`;const o=e.tagName.toLowerCase(),s=e.parentElement;if(!s)return o;const i=Array.from(s.children).filter(a=>a.tagName===e.tagName);if(i.length===1)return o;const r=i.indexOf(e)+1;return`${o}:nth-of-type(${r})`}function yt(e){return e.startsWith("__")||e.startsWith("_react")?!0:e.match(/\d{6,}/)!==null}function ge(e){return e.replace(/[^a-zA-Z0-9_-]/g,t=>`\\${t}`)}function wt(e){const n=(e.innerText??e.textContent??"").replace(/\s+/g," ").trim();return n.length===0?"":n.length<=he?n:n.slice(0,he-1)+"…"}function _t(e,t){const n={};for(const o of ht[t]){const s=e.getAttribute(o);s!==null&&s.length>0&&(n[o]=s.slice(0,200))}return n}function kt(e,t){var n;try{const o=(n=t.getComputedStyle)==null?void 0:n.call(t,e);if(!o)return!0;if(o.display==="none"||o.visibility==="hidden"||o.opacity==="0")return!1}catch{return!0}return!0}function xt(e,t){try{return Array.from(e.querySelectorAll(t))}catch{return[]}}const vt=1e4;function be(e,t){return`onboardme_snapshot_${e}_${t}`}function St(e,t){try{return sessionStorage.getItem(be(e,t))!==null}catch{return!1}}function Ct(e,t){try{sessionStorage.setItem(be(e,t),String(Date.now()))}catch{}}async function Et(e,t,n){const o=new AbortController,s=setTimeout(()=>o.abort(),vt);try{const i=await fetch(`${e}/v1/code-sources/snapshot`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":t},body:JSON.stringify(n),signal:o.signal});return{ok:i.ok,status:i.status}}catch(i){return c.warn(`snapshot send failed — ${i.message}`),{ok:!1}}finally{clearTimeout(s)}}async function At(e,t,n){if(typeof window>"u"||typeof document>"u")return{sent:!1,skipped:"error"};const o=window.location.href;if(St(e,o))return{sent:!1,skipped:"already_sent"};const s=gt(document,window);if(s.elements.length===0)return{sent:!1,skipped:"no_elements"};const{ok:i,status:r}=await Et(t,n,s);return i?(Ct(e,o),c.log(`snapshot sent (${s.elements.length} elements)`),{sent:!0,status:r}):{sent:!1,skipped:"error",status:r}}function ye(e){if(!e||typeof e!="object")return!1;const t=e;if(!Array.isArray(t.steps)||t.steps.length===0)return!1;const n=t.steps[0];if(!n)return!1;const o=n.position,s=n.action;return!(!o||typeof o.selector!="string"||typeof o.placement!="string"||!s||typeof s.type!="string"||typeof s.label!="string")}const Tt=`
265
+ .om2-overlay {
266
+ position: fixed;
267
+ inset: 0;
268
+ background: rgba(0, 0, 0, 0.4);
269
+ z-index: 2147483646;
270
+ }
271
+ .om2-tooltip,
272
+ .om2-modal-card {
273
+ position: absolute;
274
+ background: #ffffff;
275
+ color: #111827;
276
+ border-radius: 8px;
277
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 2px 8px rgba(0, 0, 0, 0.1);
278
+ padding: 16px 18px;
279
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
280
+ font-size: 14px;
281
+ line-height: 1.5;
282
+ max-width: 320px;
283
+ z-index: 2147483647;
284
+ box-sizing: border-box;
285
+ }
286
+ .om2-modal-card {
287
+ max-width: 440px;
288
+ padding: 24px 28px;
289
+ }
290
+ .om2-title {
291
+ margin: 0 0 6px 0;
292
+ font-weight: 600;
293
+ font-size: 15px;
294
+ line-height: 1.3;
295
+ }
296
+ .om2-modal-card .om2-title {
297
+ font-size: 18px;
298
+ }
299
+ .om2-description {
300
+ margin: 0 0 14px 0;
301
+ color: #4b5563;
302
+ font-size: 13px;
303
+ }
304
+ .om2-modal-card .om2-description {
305
+ font-size: 14px;
306
+ margin-bottom: 18px;
307
+ }
308
+ .om2-actions {
309
+ display: flex;
310
+ gap: 8px;
311
+ justify-content: flex-end;
312
+ align-items: center;
313
+ }
314
+ .om2-step-counter {
315
+ margin-right: auto;
316
+ font-size: 11px;
317
+ color: #9ca3af;
318
+ font-variant-numeric: tabular-nums;
319
+ }
320
+ .om2-btn {
321
+ font: inherit;
322
+ padding: 6px 14px;
323
+ border-radius: 6px;
324
+ border: none;
325
+ cursor: pointer;
326
+ font-weight: 500;
327
+ font-size: 13px;
328
+ }
329
+ .om2-btn-primary {
330
+ background: #2563eb;
331
+ color: #ffffff;
332
+ }
333
+ .om2-btn-primary:hover { background: #1d4ed8; }
334
+ .om2-btn-secondary {
335
+ background: transparent;
336
+ color: #6b7280;
337
+ }
338
+ .om2-btn-secondary:hover { color: #374151; }
339
+ .om2-highlight {
340
+ position: absolute;
341
+ pointer-events: none;
342
+ border: 2px solid #2563eb;
343
+ border-radius: 6px;
344
+ box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.18);
345
+ z-index: 2147483646;
346
+ transition: top 0.15s ease, left 0.15s ease, width 0.15s ease, height 0.15s ease;
347
+ }
348
+ `,h=8;function we(e){const{targetRect:t,tooltipWidth:n,tooltipHeight:o,scrollX:s,scrollY:i,placement:r,viewportWidth:a,viewportHeight:l}=e;let d,p;switch(r){case"top":d=t.top-o-h,p=t.left+t.width/2-n/2;break;case"bottom":d=t.top+t.height+h,p=t.left+t.width/2-n/2;break;case"left":d=t.top+t.height/2-o/2,p=t.left-n-h;break;case"right":d=t.top+t.height/2-o/2,p=t.left+t.width+h;break;case"center":default:d=l/2-o/2,p=a/2-n/2;break}return p<h&&(p=h),p+n>a-h&&(p=a-n-h),d<h&&(d=h),d+o>l-h&&(d=l-o-h),{top:d+i,left:p+s}}const L=4;function $t(e){const{targetRect:t,scrollX:n,scrollY:o}=e;return{top:t.top+o-L,left:t.left+n-L,width:t.width+L*2,height:t.height+L*2}}const _e="data-onboardme-v2-styles",It="om2-root";function Nt(e,t){if(!e.steps||e.steps.length===0)return c.warn("v2 flow has no steps — nothing to render"),null;qt(t);const n={shadowRoot:t,steps:e.steps,index:0,mounted:[],destroyed:!1};return ke(n),{destroy(){A(n)}}}function ke(e){if(e.destroyed)return;for(const n of e.mounted)n.remove();e.mounted=[];const t=e.steps[e.index];if(!t){A(e);return}t.type==="modal"?Ot(e,t):t.type==="tooltip"?Ht(e,t):Lt(e,t)}function Mt(e,t){if(t.action.type==="skip"||t.action.type==="complete"){A(e);return}if(e.index+=1,e.index>=e.steps.length){A(e);return}ke(e)}function A(e){if(!e.destroyed){e.destroyed=!0;for(const t of e.mounted)t.remove();e.mounted=[]}}function Ot(e,t){const n=k("div",{class:"om2-overlay"}),o=q(e,t,!0);o.style.top="50%",o.style.left="50%",o.style.transform="translate(-50%, -50%)",e.shadowRoot.appendChild(n),e.shadowRoot.appendChild(o),e.mounted.push(n,o)}function Ht(e,t){const n=q(e,t,!1);e.shadowRoot.appendChild(n),e.mounted.push(n);const o=xe(t.position.selector);if(!o){c.warn(`v2 tooltip selector did not match: ${t.position.selector} — centring`),ve(n);return}const s=o.getBoundingClientRect(),i=n.getBoundingClientRect(),r=we({targetRect:{top:s.top,left:s.left,width:s.width,height:s.height},tooltipWidth:i.width||280,tooltipHeight:i.height||100,scrollX:window.scrollX,scrollY:window.scrollY,placement:t.position.placement,viewportWidth:window.innerWidth||1280,viewportHeight:window.innerHeight||800});n.style.top=`${r.top}px`,n.style.left=`${r.left}px`}function Lt(e,t){const n=xe(t.position.selector);if(!n){c.warn(`v2 highlight selector did not match: ${t.position.selector}`);const d=q(e,t,!0);ve(d),e.shadowRoot.appendChild(d),e.mounted.push(d);return}const o=n.getBoundingClientRect(),s=$t({targetRect:{top:o.top,left:o.left,width:o.width,height:o.height},scrollX:window.scrollX,scrollY:window.scrollY}),i=k("div",{class:"om2-highlight"});Object.assign(i.style,{top:`${s.top}px`,left:`${s.left}px`,width:`${s.width}px`,height:`${s.height}px`});const r=q(e,t,!1);e.shadowRoot.appendChild(i),e.shadowRoot.appendChild(r),e.mounted.push(i,r);const a=r.getBoundingClientRect(),l=we({targetRect:{top:o.top,left:o.left,width:o.width,height:o.height},tooltipWidth:a.width||280,tooltipHeight:a.height||100,scrollX:window.scrollX,scrollY:window.scrollY,placement:t.position.placement,viewportWidth:window.innerWidth||1280,viewportHeight:window.innerHeight||800});r.style.top=`${l.top}px`,r.style.left=`${l.left}px`}function q(e,t,n){const o=k("div",{class:`${It} ${n?"om2-modal-card":"om2-tooltip"}`,"data-step-id":t.id}),s=k("h3",{class:"om2-title"});if(s.textContent=t.title||"",o.appendChild(s),t.description){const l=k("p",{class:"om2-description"});l.textContent=t.description,o.appendChild(l)}const i=k("div",{class:"om2-actions"}),r=k("span",{class:"om2-step-counter"});if(r.textContent=`${e.index+1} / ${e.steps.length}`,i.appendChild(r),t.action.type!=="skip"&&e.steps.length>1&&e.index<e.steps.length-1){const l=k("button",{class:"om2-btn om2-btn-secondary",type:"button"});l.textContent="Skip",l.addEventListener("click",()=>A(e)),i.appendChild(l)}const a=k("button",{class:"om2-btn om2-btn-primary",type:"button"});return a.textContent=t.action.label||(t.action.type==="complete"?"Done":"Next"),a.addEventListener("click",()=>Mt(e,t)),i.appendChild(a),o.appendChild(i),o}function k(e,t={}){const n=document.createElement(e);for(const[o,s]of Object.entries(t))o==="class"?n.className=s:n.setAttribute(o,s);return n}function xe(e){try{return document.querySelector(e)}catch{return null}}function qt(e){if(e.querySelector(`style[${_e}]`))return;const n=document.createElement("style");n.setAttribute(_e,""),n.textContent=Tt,e.appendChild(n)}function ve(e){e.style.top="50%",e.style.left="50%",e.style.transform="translate(-50%, -50%)"}let Se=!1,f=null;const Y=new Map;function Ft(e){if(Se)return c.warn("OnboardMe already initialised — ignoring duplicate init() call."),null;const t=$e(e);if(!t)return null;Se=!0,f=t,c.log(`initialised for product: ${t.productId}`);const n=Me(t.productId);c.log(`anonymous ID: ${n}`),Ue(t.eventsEndpoint??"http://localhost:3001/events",n),De();const o=Ne(t),s=o.isNew;return o.isNew?c.log(`new user detected (reason: ${o.reason})`):c.log("returning user, skipping onboarding"),t.apiFlows?(zt(t,s),Ut(t)):t.flows.length===0?c.warn("no flows defined in config — nothing to show"):F(t.flows,t,s),t.autoGenerate&&Dt(t,s),{config:t,anonymousId:n,showOnboarding:s}}async function Ut(e){e.apiFlows&&(await new Promise(t=>setTimeout(t,0)),await At(e.productId,e.apiFlows.endpoint,e.apiFlows.apiKey))}function F(e,t,n){var s;if(e.length===0)return;const o=[];for(const i of e)if(ye(i.config??i)){(s=Y.get(i.id))==null||s.destroy(),Y.delete(i.id);const r=ye(i.config)?i.config:i.steps?{steps:i.steps}:null;if(!r)continue;setTimeout(()=>{const a=Nt(r,O());a&&Y.set(i.id,a)},0)}else o.push(i);if(o.length!==0){if(e=o,n){const r=e.flatMap(a=>a.steps.filter(l=>l.type==="modal").map(l=>({step:l,flowId:a.id}))).sort((a,l)=>a.step.order-l.step.order)[0];r?setTimeout(()=>{Be(r.step,O(),t.productId,r.flowId)},0):c.log("no modal step found — skipping welcome modal")}for(const i of e){if(!i.steps.some(a=>a.type==="checklist"))continue;const r=ae(t.productId,i.id);setTimeout(()=>{Ke(i,r,O(),t.debug??!1,t.productId,i.id)},0);break}}}async function zt(e,t){if(!e.apiFlows)return;const n=`onboardme_flows_${e.productId}`,o=await pt(e.apiFlows.endpoint,e.apiFlows.apiKey,n);if(o.flows.length===0){c.warn("no flows from API and no cache available");return}f&&(f={...f,flows:o.flows}),F(o.flows,e,t),ft(e.apiFlows.endpoint,e.apiFlows.apiKey,n,s=>{f&&(f={...f,flows:s}),F(s,e,t)})}async function Dt(e,t){if(!e.autoGenerate)return;const n=nt(),o=await lt(e.autoGenerate.endpoint,e.productId,n,e.autoGenerate.cacheTtlMs);if(o.length===0)return;const s=new Set(e.flows.map(r=>r.id)),i=o.filter(r=>!s.has(r.id));i.length!==0&&(f&&(f={...f,flows:at(f.flows,o)}),F(i,e,t))}function Pt(e){if(f)for(const t of f.flows){if(e!==t.completionGoal)continue;const n=`onboardme_flow_done_${f.productId}_${t.id}`;localStorage.getItem(n)||(localStorage.setItem(n,"1"),y("flow_completed",{flowId:t.id}),y("goal_reached",{flowId:t.id}),Qe(O()))}}let U=!1;const x={init(e){if(U){c.warn("init() called more than once — ignoring duplicate call.");return}e!=null&&e.debug&&Te(!0),Ft(e)&&(U=!0,jt())},identify(e,t){if(!U){c.warn("identify() called before init() — call will be ignored.");return}ze(e),c.log(`identify: ${e}`,t)},track(e,t){if(!U){c.warn("track() called before init() — call will be ignored.");return}y("step_action_taken",{properties:{eventName:e,...t??{}}}),Pt(e),c.log(`track: ${e}`,t)}};function jt(){const e=window.OnboardMe,t=e==null?void 0:e._q;if(!(!Array.isArray(t)||t.length===0)){c.log(`replaying ${t.length} queued call(s)`);for(const[n,o]of t)n==="identify"?x.identify(o[0],o[1]):n==="track"&&x.track(o[0],o[1]);e&&(e._q=[])}}return window.OnboardMe=x,x}();
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "onboardme-sdk",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "main": "./dist/sdk.iife.js",
6
+ "scripts": {
7
+ "build": "vite build && node ../../scripts/check-bundle-size.mjs",
8
+ "dev": "vite build --watch",
9
+ "test": "vitest run",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@onboardme/types": "workspace:*"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^20.0.0",
17
+ "jsdom": "^29.0.1",
18
+ "typescript": "^5.4.0",
19
+ "vite": "^5.2.0",
20
+ "vitest": "^1.4.0"
21
+ }
22
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Day 1 Tests — Monorepo Setup
3
+ *
4
+ * These tests check that the SDK package itself is wired up correctly:
5
+ * the entry point exports the right thing and the init function exists.
6
+ * (Bundle size is verified separately by the build script.)
7
+ */
8
+
9
+ import { describe, it, expect } from 'vitest';
10
+ import OnboardMe from '../index.js';
11
+
12
+ describe('Day 1 — SDK entry point', () => {
13
+ it('the SDK exports an object (not undefined, not null)', () => {
14
+ expect(OnboardMe).toBeDefined();
15
+ expect(OnboardMe).not.toBeNull();
16
+ });
17
+
18
+ it('the SDK has an init function', () => {
19
+ expect(typeof OnboardMe.init).toBe('function');
20
+ });
21
+
22
+ it('calling init with a valid config does not throw any error', () => {
23
+ expect(() => {
24
+ OnboardMe.init({
25
+ productId: 'test-product',
26
+ flows: [],
27
+ });
28
+ }).not.toThrow();
29
+ });
30
+
31
+ it('calling init multiple times does not throw any error', () => {
32
+ expect(() => {
33
+ OnboardMe.init({ productId: 'p1', flows: [] });
34
+ OnboardMe.init({ productId: 'p1', flows: [] });
35
+ }).not.toThrow();
36
+ });
37
+ });