@sightspool/sdk 0.3.0 → 0.4.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 (41) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +128 -329
  3. package/SECURITY.md +16 -0
  4. package/dist/index.cjs +230 -1225
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +20 -41
  7. package/dist/index.d.ts +20 -41
  8. package/dist/index.js +228 -757
  9. package/dist/index.js.map +1 -1
  10. package/dist/release.json +21 -0
  11. package/dist/sdk.global.js +2 -59
  12. package/dist/sdk.global.js.map +1 -1
  13. package/dist/source.json +11 -0
  14. package/package.json +14 -12
  15. package/site/CNAME +1 -0
  16. package/site/demo.html +6 -0
  17. package/site/demo.js +36 -0
  18. package/site/index.html +74 -0
  19. package/site/release.js +20 -0
  20. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/release.json +21 -0
  21. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js +3 -0
  22. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js.map +1 -0
  23. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/source.json +11 -0
  24. package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/release.json +21 -0
  25. package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/sdk.global.js +3 -0
  26. package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/sdk.global.js.map +1 -0
  27. package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/source.json +11 -0
  28. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/release.json +21 -0
  29. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js +3 -0
  30. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js.map +1 -0
  31. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/source.json +11 -0
  32. package/site/styles.css +69 -0
  33. package/site/trust.html +36 -0
  34. package/dist/chunk-4PADVIIO.js +0 -41
  35. package/dist/chunk-4PADVIIO.js.map +0 -1
  36. package/dist/probe-BNGMQQ5C.js +0 -113
  37. package/dist/probe-BNGMQQ5C.js.map +0 -1
  38. package/dist/prompt-GDFYC75D.js +0 -139
  39. package/dist/prompt-GDFYC75D.js.map +0 -1
  40. package/dist/survey-CICJQ5VI.js +0 -145
  41. package/dist/survey-CICJQ5VI.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ ## 0.4.1 — unpublished local candidate
2
+
3
+ - Replace popup interview delivery with one persistent corner panel.
4
+ - Keep open sessions mounted across minimize and recruitment cleanup.
5
+ - Restrict iframe permissions and validate parent message origin/source.
6
+
7
+ # Changelog
8
+
9
+ ## 0.4.0 — prepared, unpublished
10
+
11
+ - Replace the public capture entry with research invitation delivery.
12
+ - Require an explicit all_visitors or signed_in audience; no fabricated visitor identity.
13
+ - Retain only local presence of identity, with no user IDs in offer payloads.
14
+ - Omit cookies and Referer from offer requests; pause, logout and cleanup reject late responses.
15
+ - Add versioned content-addressed browser files, SHA-384 integrity, source snapshots,
16
+ measured bundle sizes and manifest checks before publication.
17
+ - Add data/security documentation and an actual-SDK demo with simulated transport.
18
+
19
+ The app/backend handles participant consent, recording, transcription and analysis
20
+ separately. No independent security audit or new hosted acceptance is claimed.
21
+ Earlier capture API documentation is archived in docs/legacy.
package/README.md CHANGED
@@ -1,384 +1,183 @@
1
1
  # @sightspool/sdk
2
2
 
3
- In-product capture for [Sightspool](https://github.com/sightspool). Drop it into your web app and
4
- it captures, **at the moment of friction**, what a user was trying to do (*intent*),
5
- how hard it was (*effort*), and the account behind it emitting one linked **Signal**
6
- into your Sightspool workspace.
3
+ Embed Sightspool research in your product. The SDK invites website visitors and signed-in users into
4
+ owner-approved research during User Hours, then opens Sightspool for consent and
5
+ the interview. It does not capture page content, behavior, form values or analytics.
7
6
 
8
- It's the one source of data that exists nowhere else: the *silent failures* (the user
9
- who calmly couldn't do the thing, hit no error, filed no ticket, and left) and the
10
- *unmet demand* (goals your product has no path for, so no funnel or error ever records
11
- them).
7
+ This is the **0.4.0 research API**, a deliberate change from the old capture SDK.
8
+ Install the exact version below and review upgrades before deploying them.
9
+ Old capture code and documentation are archived in `docs/legacy`.
12
10
 
13
- > **Status:** v0.1, collect-side. The SDK *senses* — it captures and analyses. It does
14
- > **not** act on your surface (surveys/nudges/experiments are Wave 0005, and every one
15
- > is human-gated). Trigger sensitivity and intent inference calibrate with live traffic.
11
+ Read the [data and security guide](https://www.sightspool.com/sdk/data-security),
12
+ inspect the [public source](https://github.com/sightspool/sdk), or try the
13
+ [isolated SDK demo](https://www.sightspool.com/sdk/demo).
16
14
 
17
- **Docs:** [full reference + CSP & framework guides](https://sdk.sightspool.com/) ·
18
- [llms.txt](https://sdk.sightspool.com/llms.txt) (the machine-readable install/CSP/config
19
- doc for agents) · [npm](https://www.npmjs.com/package/@sightspool/sdk)
15
+ ## npm
20
16
 
21
- ---
17
+ ```sh
18
+ npm install --save-exact @sightspool/sdk@0.4.1
19
+ ```
22
20
 
23
- ## Install — two lines
21
+ ```ts
22
+ import Sightspool from '@sightspool/sdk'
24
23
 
25
- ### npm / bundler
24
+ Sightspool.init({ key: 'YOUR_GO_LIVE_WIDGET_UUID', audience: 'all_visitors' })
25
+ // Optional when a visitor signs in:
26
+ Sightspool.identify(currentUser.id)
26
27
 
27
- ```bash
28
- npm install @sightspool/sdk
28
+ // On logout (anonymous visitors remain eligible in all_visitors mode):
29
+ Sightspool.identify(null)
30
+
31
+ // When leaving the pages included in your research:
32
+ Sightspool.destroy()
29
33
  ```
30
34
 
31
- ```js
32
- import Sightspool from '@sightspool/sdk'
35
+ Choose an explicit `audience` matching the approved interview plan:
33
36
 
34
- Sightspool.init({ key: 'pk_live_…' })
37
+ - `all_visitors`: anonymous website visitors and signed-in users. Initialize once on the relevant website pages. No identity or login is required; logout continues recruitment as a visitor.
38
+ - `signed_in`: only users with a real signed-in session. Initialize in your authenticated app shell and call `identify(actualUserId)` after authentication resolves. Until then, no request or invitation is made. `identify(null)` immediately removes invitations on logout.
35
39
 
36
- // once you know who the user is:
37
- Sightspool.identify(currentUser.id, { account: 'Vertex Logistics', plan: 'growth' })
38
- ```
40
+ There is no default audience. Missing or invalid audience configuration stays idle.
41
+ `identify` retains only whether an ID is present; the ID itself is not stored or
42
+ transmitted. Do not invent IDs for visitors. This client setting controls display;
43
+ it does not authenticate a participant or change the server-approved research cohort.
39
44
 
40
- `init` boots passive capture immediately. `identify` attaches the user to an **account**
41
- and **plan** — the only required wiring, and it's what lets Sightspool rank by customer
42
- (and, with a connected billing source, by MRR). Most apps already make an equivalent
43
- call for their analytics/support tools.
45
+ ## Script tag
44
46
 
45
- ### Script tag (no build step)
47
+ For visitors, no authentication integration is needed:
46
48
 
47
49
  ```html
48
- <script
49
- async
50
- src="https://www.sightspool.com/sdk.global.js"
51
- data-sightspool-key="pk_live_…"
52
- ></script>
50
+ <script async src="https://www.sightspool.com/sdk.global.js"
51
+ data-sightspool-key="YOUR_GO_LIVE_WIDGET_UUID"
52
+ data-sightspool-audience="all_visitors"></script>
53
53
  ```
54
54
 
55
- The tag auto-`init`s from its `data-sightspool-key`. Call `identify` once the user is
56
- known:
55
+ For signed-in-only research, listen for readiness before loading the SDK, then
56
+ synchronize your actual session:
57
57
 
58
58
  ```html
59
59
  <script>
60
- window.Sightspool && window.Sightspool.identify(userId, { account, plan })
60
+ let sightspoolUserId = null;
61
+ function syncSightspoolUser(userId) {
62
+ sightspoolUserId = userId;
63
+ window.Sightspool?.identify(userId);
64
+ }
65
+ window.addEventListener('sightspool:ready', () => {
66
+ window.Sightspool.identify(sightspoolUserId);
67
+ });
68
+ // Call syncSightspoolUser(actualUser.id) when authentication resolves.
69
+ // Call syncSightspoolUser(null) on logout.
61
70
  </script>
71
+ <script async src="https://www.sightspool.com/sdk.global.js"
72
+ data-sightspool-key="YOUR_GO_LIVE_WIDGET_UUID"
73
+ data-sightspool-audience="signed_in"></script>
62
74
  ```
63
75
 
64
- (Loading the script before `Sightspool` is defined? Calls are safe to make against
65
- `window.Sightspool` once the script has loaded; until then, guard with `&&` as above.)
66
-
67
- The script tag also reads these optional attributes (the no-build equivalent of the
68
- `init` options — comma-separate selector lists):
69
-
70
- ```html
71
- <script
72
- async
73
- src="https://www.sightspool.com/sdk.global.js"
74
- data-sightspool-key="pk_live_…"
75
- data-sightspool-block=".billing-panel, [data-private]"
76
- data-sightspool-redact=".customer-name"
77
- data-sightspool-debug
78
- data-sightspool-capture-localhost
79
- ></script>
80
- ```
81
-
82
- ---
83
-
84
- ## Configuration
85
-
86
- `init(config)` — all optional except `key`:
87
-
88
- | option | type | default | purpose |
89
- |---|---|---|---|
90
- | `key` | `string` | — | **required.** Your publishable key (`pk_test_…` / `pk_live_…`), from the Connections → In-product SDK card. Publishable — safe to ship in client JS. See [Keys & environments](#keys--environments). |
91
- | `endpoint` | `string` | the bundle's origin (script tag) / `https://www.sightspool.com` (npm) | Ingest base URL. The `<script>` install auto-resolves it to wherever `sdk.global.js` was served from (your app), so the key alone is enough; override for a CDN-hosted bundle or dev. |
92
- | `boundaryAsk` | `boolean` | `true` | Show the one-tap "did you do what you came to do?" ask at session boundaries. |
93
- | `interventions` | `boolean` | `true` | Show human-approved **surveys** served by your Sightspool workspace (see [Interventions](#interventions-surveys)). Set `false` to capture only. |
94
- | `consent` | `boolean` | `true` | Start capturing immediately. Set `false` to stay paused until you call `Sightspool.consent(true)` (or `start()`) after obtaining consent. |
95
- | `redact` | `string[]` | `[]` | CSS selectors whose captured text is **masked** (replaced with `‹redacted›`) before anything leaves the page. The event is still recorded — only its label is masked. |
96
- | `block` | `string[]` | `[]` | CSS selectors whose events are **dropped entirely** (the hard opt-out). Equivalent to putting `data-sightspool-ignore` on the element. |
97
- | `captureOnLocalhost` | `boolean` | `false` | By default the SDK **no-ops on localhost** (`localhost`, `127.0.0.1`, `*.local`, `*.localhost`) so your `npm run dev` traffic never pollutes analytics. Set `true` to capture locally (e.g. to test the install). |
98
- | `debug` | `boolean` | `false` | Log every capture decision to the console (`[sightspool] …`) so you can watch it work. |
99
-
100
- Server-side config (allowed CORS origins, additional redaction rules) lives on the
101
- Connections card and is enforced at ingest — the key alone can't post from an
102
- un-allowlisted origin.
103
-
104
- ### Privacy controls at a glance
105
-
106
- | You want to… | Use |
107
- |---|---|
108
- | Never capture a subtree (e.g. a billing panel) | `data-sightspool-ignore` on the element, or a `block` selector |
109
- | Mask a field's text but still log the interaction | a `redact` selector (text → `‹redacted›`) |
110
- | Wait for cookie-banner consent | init `{ consent: false }` then `Sightspool.consent(true)` |
111
- | Keep dev traffic out of analytics | nothing — localhost is suppressed by default |
112
-
113
- ---
114
-
115
- ## Keys & environments
116
-
117
- Your key is **publishable** — safe to ship in client JS (the Stripe `pk_` model). Two
118
- prefixes, one per environment:
119
-
120
- | prefix | use it for |
121
- |---|---|
122
- | `pk_test_…` | development / staging / preview deploys |
123
- | `pk_live_…` | production |
124
-
125
- The SDK treats both prefixes **identically** — there's no client-side special-casing; the
126
- prefix tells *Sightspool* (at ingest) which environment a Signal belongs to, so test traffic
127
- never mixes into production analytics. Issue both from **Connections → In-product SDK**.
128
-
129
- > The SDK also **no-ops on localhost** by default (see `captureOnLocalhost`), so even a
130
- > `pk_live_` key won't capture from `npm run dev`. Test keys are for *deployed* non-prod
131
- > environments (staging, previews).
132
-
133
- Keep the key in an environment variable rather than hardcoding it, and pick test vs live by
134
- environment. The key is exposed to the browser, so use your framework's **client** env-var
135
- prefix (`NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, …) — it's publishable, so that's expected:
136
-
137
- ```js
138
- Sightspool.init({ key: process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY })
139
- ```
140
-
141
- ```bash
142
- # .env.development / preview
143
- NEXT_PUBLIC_SIGHTSPOOL_KEY=pk_test_…
144
- # .env.production
145
- NEXT_PUBLIC_SIGHTSPOOL_KEY=pk_live_…
146
- ```
76
+ Go live supplies the correct key and endpoint. Local keys belong to the local
77
+ Sightspool database and cannot be paired with production. Script installs default
78
+ to the origin serving the bundle; `data-sightspool-endpoint` overrides that for a
79
+ CDN or self-hosted install. npm defaults to `https://www.sightspool.com`.
147
80
 
148
- ---
81
+ ## API
149
82
 
150
- ## Framework integration
83
+ | Method | Behavior |
84
+ | --- | --- |
85
+ | `init({ key, audience, endpoint? })` | Start research for the explicit audience; UUID key required. Repeating the same configuration is idempotent. Changing it tears down the previous runtime. |
86
+ | `identify(userId)` | Enable signed-in eligibility; only its boolean presence stays locally. `null` clears it; signed-in-only invitations disappear, all-visitors recruitment continues. |
87
+ | `pause()` | Pause recruitment, abort the current request and remove the launcher. |
88
+ | `resume()` | Resume recruitment for the configured audience. Does not bypass approval or hours. |
89
+ | `destroy()` | Remove the launcher, listeners and polling. An already opened interview is not silently ended. |
90
+ | `getStatus()` | `not_initialized`, `signed_out`, `paused`, `checking`, `unavailable`, `available` or `error`. |
151
91
 
152
- ### Next.js (App Router) `next/script`
92
+ `pause` and `resume` are recruitment controls, not consent to record. Participants
93
+ still review the offer, eligibility and recording consent in Sightspool. Owners
94
+ approve the research setup separately. The backend remains authoritative for
95
+ availability, capacity, signed offers and interview admission.
153
96
 
154
- The idiomatic install is `next/script`, not a raw `<script>`. Add it once in your root
155
- layout — the tag auto-`init`s from `data-sightspool-key`:
97
+ ## React / Next.js
156
98
 
157
- ```tsx
158
- // app/layout.tsx
159
- import Script from 'next/script'
160
-
161
- export default function RootLayout({ children }: { children: React.ReactNode }) {
162
- return (
163
- <html lang="en">
164
- <body>
165
- {children}
166
- <Script
167
- src="https://www.sightspool.com/sdk.global.js"
168
- data-sightspool-key={process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY}
169
- strategy="afterInteractive"
170
- />
171
- </body>
172
- </html>
173
- )
174
- }
175
- ```
176
-
177
- Then `identify` the user once known (e.g. a client component after auth):
99
+ Use a client component on the pages included in your research. Identity is optional for all visitors:
178
100
 
179
101
  ```tsx
180
102
  'use client'
181
- useEffect(() => {
182
- window.Sightspool?.identify(user.id, { account: user.account, plan: user.plan })
183
- }, [user])
184
- ```
185
-
186
- > `data-sightspool-key` is inlined at build time, so `NEXT_PUBLIC_SIGHTSPOOL_KEY` must be set
187
- > in the environment Next builds in. `strategy="afterInteractive"` keeps it off the critical
188
- > path.
189
-
190
- ### React / Next.js — `@sightspool/react`
191
-
192
- For React apps, use the official bindings — a declarative provider over the core SDK:
193
-
194
- ```bash
195
- npm install @sightspool/react @sightspool/sdk react
196
- ```
197
-
198
- ```tsx
199
- import { SightspoolProvider } from '@sightspool/react'
200
-
201
- <SightspoolProvider
202
- apiKey={process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY!}
203
- identity={user && { userId: user.id, account: user.account, plan: user.plan }}
204
- >
205
- <App />
206
- </SightspoolProvider>
207
- ```
208
-
209
- It ships a `"use client"` banner (drops straight into a Next.js App Router **server**
210
- layout), re-fires `identify` whenever `identity` changes, and takes a reactive `consent`
211
- prop for cookie banners. Full API:
212
- [@sightspool/react](https://www.npmjs.com/package/@sightspool/react) ·
213
- [packages/react](https://github.com/sightspool/sdk/tree/main/packages/react).
214
-
215
- <details><summary>Or wire the core SDK by hand (no wrapper)</summary>
216
-
217
- ```tsx
218
103
  import { useEffect } from 'react'
219
104
  import Sightspool from '@sightspool/sdk'
220
105
 
221
- export function SightspoolBoot({ userId, account, plan }) {
222
- useEffect(() => { Sightspool.init({ key: import.meta.env.VITE_SIGHTSPOOL_KEY }) }, [])
223
- useEffect(() => { if (userId) Sightspool.identify(userId, { account, plan }) }, [userId, account, plan])
106
+ export function Research({ userId = null }: { userId?: string | null }) {
107
+ useEffect(() => {
108
+ Sightspool.init({ key: 'YOUR_GO_LIVE_WIDGET_UUID', audience: 'all_visitors' })
109
+ Sightspool.identify(userId)
110
+ return () => Sightspool.destroy()
111
+ }, [userId])
224
112
  return null
225
113
  }
226
114
  ```
227
- </details>
228
-
229
- ---
230
-
231
- ## Content-Security-Policy
232
115
 
233
- If your app sets a CSP, allow the SDK's two footprints — the **script load** and the
234
- **ingest beacon**. With the standard install they're the *same host* (the bundle is served
235
- from the app it ingests to), so it's one host in two directives:
116
+ Optional `@sightspool/react` 0.2.0 provides `SightspoolProvider` and research hooks.
117
+ It requires SDK 0.4.x; see `packages/react/README.md`. Use one instance per document.
236
118
 
237
- **Script-tag install**
238
-
239
- ```
240
- script-src https://www.sightspool.com;
241
- connect-src https://www.sightspool.com;
242
- ```
243
-
244
- **npm / bundler install** — the SDK is bundled into your own first-party JS, so no
245
- `script-src` host is needed; only the ingest origin:
246
-
247
- ```
248
- connect-src https://www.sightspool.com;
249
- ```
119
+ ## Privacy and verification
250
120
 
251
- If you pass a custom `endpoint`, use *that* origin in `connect-src`. The beacon goes via
252
- `navigator.sendBeacon` with a `fetch(keepalive)` fallback both governed by `connect-src`.
121
+ The offer request contains only the public workspace key, a random per-workspace
122
+ browser-session device token, and the operation name. No user ID, traits, auth
123
+ secrets, DOM text or page URL is included. Requests use `credentials: 'omit'`.
124
+ Offer requests explicitly suppress the Referer header; Origin and ordinary network metadata still reach the service.
125
+ No microphone or recording starts on initialization or launcher display.
253
126
 
254
- - **`strict-dynamic` / nonce.** Under `script-src 'strict-dynamic'`, host allowlists are
255
- ignored for scripts give the `<script>` tag your per-request nonce (`nonce={nonce}` in
256
- Next) so it's trusted. `connect-src` still needs the ingest host.
257
- - **Prompt styles.** The one-tap prompt renders into a **shadow root** and injects its own
258
- `<style>`. Under a strict `style-src` without `'unsafe-inline'`, those styles may not apply
259
- — the prompt stays **fully functional but unstyled** (the SDK never throws into your page).
260
- Add `'unsafe-inline'` to `style-src` if you want it styled.
127
+ Polling occurs at most once per 15-second interval while visible and eligible for the configured audience,
128
+ plus explicit identity/resume/visibility changes. Only one request is in flight;
129
+ requests time out after 10 seconds. Identity changes, pause, hidden tabs and destruction invalidate stale responses.
130
+ Logout removes signed-in-only invitations; all-visitors mode refreshes as a visitor.
261
131
 
262
- ---
132
+ A connection receipt proves that the SDK contacted the configured server. It does
133
+ not certify your app's complete login/logout flow or a successful interview. Test
134
+ anonymous visits, login, navigation, logout, slow loading and unmounting in both modes. Use Go live
135
+ for server connection checks. A closed User Hours window can connect without an
136
+ invitation. Do not fabricate approval or weaken an origin/CSP policy to make it show.
263
137
 
264
- ## Interventions (surveys)
138
+ ## Verified script releases
265
139
 
266
- The SDK is two-way. Besides *capturing*, it can show a **human-approved survey** at the
267
- moment of friction — the "ask" side of Sightspool. You don't author these in code: your
268
- team proposes a survey off a proven finding in the app, **a human approves it**, sets who
269
- sees it (route / account / plan), and the SDK serves it. Nothing reaches a user without
270
- that approval ("no proof **and** no approval, no act").
140
+ Go live generates a versioned, content-addressed script URL with SHA-384 integrity,
141
+ `crossorigin="anonymous"` and `referrerpolicy="no-referrer"`. Use those values together.
142
+ The unversioned script examples above are a convenience URL whose contents can change.
143
+ To pin a reviewed release, use Go live or the manifest linked from the data and security guide.
271
144
 
272
- It's on by default — the same one or two install lines that capture also serve. When the
273
- SDK loads (and on each route change) it asks your workspace *"anything to show this user
274
- here?"*; if there's a matching approved survey, it renders a small card in a shadow root
275
- (your CSS can't reach it; it leaks no styles), and posts the answer back.
145
+ For self-hosting, copy the reviewed bundle and preserve its integrity value. Set
146
+ `data-sightspool-endpoint` to the workspace Sightspool origin explicitly, because
147
+ script auto-init otherwise defaults to the bundle host. Keep your own CSP and nonce
148
+ requirements; allow only the required script host and Sightspool connection origin.
276
149
 
277
- ```js
278
- Sightspool.init({ key: "pk_live_…" }); // serving is on by default
279
- Sightspool.init({ key: "pk_live_…", interventions: false }); // capture only
280
- ```
150
+ ## Maintainers
281
151
 
282
- ```html
283
- <!-- script-tag install: opt out with one attribute -->
284
- <script src="https://www.sightspool.com/sdk.global.js"
285
- data-sightspool-key="pk_live_…"
286
- data-sightspool-no-interventions></script>
152
+ ```sh
153
+ pnpm install --frozen-lockfile
154
+ pnpm -r --include-workspace-root build
155
+ pnpm -r --include-workspace-root test
156
+ npm publish --dry-run
287
157
  ```
288
158
 
289
- **What shows, and how often** all server-gated, so you stay in control:
290
-
291
- - **Targeting** only to the route / account / plan the approver chose.
292
- - **De-dup** a survey a user has answered never reappears (a per-user key is kept
293
- locally; it's anonymous unless you've called `identify()`).
294
- - **Won't pester** — at most one survey on screen, one per session, and it shares a
295
- cooldown with the boundary ask so the two never stack back-to-back. The run also stops
296
- itself once it hits the approver's response target.
297
-
298
- The survey widget is **lazily loaded** — it splits into its own chunk and adds nothing to
299
- your bundle until a survey is actually served. Today the SDK renders one-tap / short-text
300
- surveys; richer types (incl. voice micro-research) render as they ship.
301
-
302
- ## What it captures
303
-
304
- - **Passively, no wiring** — route/screen sequence, clicks, **dead-clicks** and
305
- **rage-clicks**, client-side errors and failed requests, and a rolling trail of the
306
- last meaningful events.
307
- - **Stated intent** — typed queries in search / filter / command-palette / empty-state
308
- inputs, especially **zero-result** searches (the highest-signal intent, no question).
309
- - **The one-tap ask** (at a session boundary or after detected friction, rate-limited
310
- and fatigue-aware): *"Were you able to do what you came here to do today?"* → on "Not
311
- really," a short shortlist of likely goals + an always-present "Something else."
312
-
313
- Each capture emits one **Signal** (`intent + path + account + effort`). Intent and
314
- effort are *constructed* server-side with calibrated confidence — the SDK ships the
315
- raw trace and the answer; it never guesses.
316
-
317
- ---
159
+ The npm and browser builds share `src/research.ts`. The Sightspool app copies
160
+ `sdk.global.js` from this package and serves `research-widget.js` as a byte-identical
161
+ alias for recent internal snippets. Neither build imports the former capture engine.
162
+ See `docs/research-sdk-transition.md` for release sequencing. Apache-2.0.
318
163
 
319
- ## Safety & privacy
164
+ ## Embedded interview panel (0.4.1, local candidate)
320
165
 
321
- Privacy-conscious **by default** these are on without any config:
166
+ An available invitation opens one bottom-right panel on the product page. Consent,
167
+ waiting, supported founder audio or Sightspool text conversation, and completion
168
+ remain inside its isolated Sightspool iframe. Minimize or Escape hides the panel;
169
+ Return to interview reopens the same session. Minimize does not mute, end or withdraw.
170
+ Use the explicit in-panel controls to stop audio or delete interview evidence.
322
171
 
323
- - **PII is masked before it leaves the page.** Emails and long digit runs (card /
324
- account-number-ish) in any captured label are replaced with `‹email›` / `‹num›`.
325
- Password, email, `tel`, and credit-card inputs are dropped entirely their values
326
- are **never** captured.
327
- - **No cookies, no `localStorage`, no raw keystrokes.** The SDK reads none of them. It
328
- captures *debounced* search-input values (stated intent) and interaction events not
329
- a keylog.
330
- - **You control the rest.** Drop any subtree with `data-sightspool-ignore` or a `block`
331
- selector; mask a field's text with a `redact` selector; gate everything behind
332
- `Sightspool.consent(false)` until your cookie banner says otherwise.
333
- - **Suppressed on localhost** so dev traffic never pollutes analytics (opt back in with
334
- `captureOnLocalhost`).
172
+ Allow the configured Sightspool origin in your site's `frame-src` policy and
173
+ permit microphone delegation to that origin if founder audio is used. Preserve
174
+ other CSP and Permissions Policy restrictions; a host policy may intentionally
175
+ block audio. The iframe requests a microphone only after explicit participant action.
176
+ The frame is restricted to the workspace's saved product origin and exchanges only
177
+ a minimize message with its parent, never interview text or audio. There is no popup.
335
178
 
336
- And the engineering guarantees:
337
-
338
- - **Never throws into your app.** Every path is wrapped; capture degrades silently
339
- (no Signal is worth a broken host UI).
340
- - **Tiny + non-blocking.** The prompt UI lazy-loads into a shadow root, so it can't be
341
- styled-broken by your CSS and adds ~nothing to your bundle until it's needed.
342
- - **Processor posture.** Sightspool processes on your behalf; the prompt discloses that
343
- diagnostic context is attached.
344
-
345
- ---
346
-
347
- ## API
348
-
349
- ```ts
350
- Sightspool.init(config: SightspoolConfig): void
351
- Sightspool.identify(userId: string, traits?: { account?: string; plan?: string }): void
352
- Sightspool.consent(granted: boolean): void // runtime consent toggle (wire to your cookie banner)
353
- Sightspool.start(): void // begin capture if init'd with { consent: false } (alias of consent(true))
354
- Sightspool.stop(): void // pause capture and flush (alias of consent(false))
355
- ```
356
-
357
- ---
358
-
359
- ## Roadmap (not yet built)
360
-
361
- Deliberately deferred from the v1 collect side. Most are **data-gated** — they need real
362
- traffic to calibrate, so they wait for the first production installs.
363
-
364
- - **Server-LLM State-B candidates** (`/api/sdk/candidates`). Today the prompt's candidate goals
365
- are derived **locally** (recent search query + page label) — instant and free, but shallow. A
366
- server endpoint would generate sharper candidates in the app's own feature vocabulary, at the
367
- cost of a per-prompt round-trip; it must fall back to the local/generic ask within a tight
368
- latency budget.
369
- - **Adaptive micro-interview** on high-value / high-MRR friction — a short, session-grounded,
370
- agent-authored follow-up beyond the one-tap default, under the same fatigue caps.
371
- - **Reliable zero-result detection.** v1 harvests the typed query; flagging it as *zero-result*
372
- (the highest-signal intent) is best-effort and needs per-app empty-state hints.
373
- - **Server-to-server signed (HMAC) ingest** — for non-browser / backend Signal sources (the
374
- publishable-key + origin-allowlist posture is browser-only).
375
- - **Mobile / native SDK** — web-first for now.
376
-
377
- ## Develop
378
-
379
- ```bash
380
- pnpm install
381
- pnpm build # tsup → dist/ (ESM + CJS + types + dist/sdk.global.js)
382
- pnpm type-check
383
- pnpm test # node --test over the pure cores
384
- ```
179
+ Once opened, the panel stays mounted across visibility changes, pause, identity
180
+ changes and SDK destroy/remount, so recruitment cleanup cannot silently end a call.
181
+ Destroy stops further recruitment. The active panel remains reachable until the
182
+ page is left; a full page navigation may interrupt audio. Minimize preserves the
183
+ session only within the current document.
package/SECURITY.md ADDED
@@ -0,0 +1,16 @@
1
+ # Security policy
2
+
3
+ Report suspected vulnerabilities in the Sightspool SDK privately to
4
+ security@sightspool.com. Include the package version, affected feature, impact and
5
+ a minimal reproduction without customer data or secrets. Do not open a public
6
+ issue containing exploit details or private information.
7
+
8
+ The existing disclosure policy is https://www.sightspool.com/.well-known/security.txt.
9
+ It covers the public SDK packages and Sightspool service, not testing a customer's
10
+ website without their authorization. There is no paid bounty or new response-time
11
+ commitment implied here.
12
+
13
+ Research SDK 0.4.0 is prepared locally and has not had an independent security audit.
14
+ Automated tests and release integrity checks are not a security certification.
15
+ The legacy capture API is retired; it is not the supported research installation.
16
+ For data access/deletion requests, contact privacy@sightspool.com.