@sightspool/sdk 0.3.0 → 0.4.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/CHANGELOG.md +15 -0
- package/README.md +111 -333
- package/SECURITY.md +16 -0
- package/dist/index.cjs +159 -1226
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -41
- package/dist/index.d.ts +20 -41
- package/dist/index.js +160 -761
- package/dist/index.js.map +1 -1
- package/dist/release.json +21 -0
- package/dist/sdk.global.js +2 -59
- package/dist/sdk.global.js.map +1 -1
- package/dist/source.json +11 -0
- package/package.json +14 -12
- package/site/CNAME +1 -0
- package/site/demo.html +6 -0
- package/site/demo.js +27 -0
- package/site/index.html +74 -0
- package/site/release.js +20 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/release.json +21 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js +3 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js.map +1 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/source.json +11 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/release.json +21 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js +3 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js.map +1 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/source.json +11 -0
- package/site/styles.css +69 -0
- package/site/trust.html +36 -0
- package/dist/chunk-4PADVIIO.js +0 -41
- package/dist/chunk-4PADVIIO.js.map +0 -1
- package/dist/probe-BNGMQQ5C.js +0 -113
- package/dist/probe-BNGMQQ5C.js.map +0 -1
- package/dist/prompt-GDFYC75D.js +0 -139
- package/dist/prompt-GDFYC75D.js.map +0 -1
- package/dist/survey-CICJQ5VI.js +0 -145
- package/dist/survey-CICJQ5VI.js.map +0 -1
package/site/trust.html
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Data & security — Sightspool SDK</title><meta name="description" content="Inspect what the Sightspool research SDK sends, stores and does. Review its source, exact release, controls and interview data boundaries."><link rel="canonical" href="https://www.sightspool.com/sdk/data-security"><link rel="stylesheet" href="styles.css"><script src="release.js" defer></script></head>
|
|
3
|
+
<body><main class="wrap">
|
|
4
|
+
<nav aria-label="SDK documentation"><a href="index.html">SDK guide</a><a href="trust.html" aria-current="page">Data & security</a><a href="demo.html">Try the SDK</a><a href="https://github.com/sightspool/sdk">GitHub</a></nav>
|
|
5
|
+
<header><p class="muted">Sightspool research SDK · reviewed 7 September 2026</p><h1>Know what you’re installing.</h1><p class="lede">A small invitation widget for your website. Review its data flow, inspect the code and choose when it runs.</p><p class="status">These documents describe the 0.4.0 research API, which replaces the earlier capture SDK. No independent security audit is claimed.</p></header>
|
|
6
|
+
<div class="cards"><div class="card"><strong>No passive page capture</strong>No collection of page text, form values, clicks, keystrokes or browsing history by this research SDK.</div><div class="card"><strong>No login required for visitors</strong>Choose all visitors or signed-in users. A supplied user ID stays out of the request payload.</div><div class="card"><strong>Participation is a separate choice</strong>Installing the SDK does not start a microphone, recording or interview.</div></div>
|
|
7
|
+
<nav aria-label="On this page"><a href="#data">Data inventory</a><a href="#interviews">After joining</a><a href="#verify">Verify a release</a><a href="#controls">Your controls</a><a href="#security">Security & limits</a></nav>
|
|
8
|
+
<section id="data"><h2>Before someone joins</h2><p>The SDK asks whether an approved interview invitation is available. Its application request body contains exactly these three fields:</p><pre><code>{
|
|
9
|
+
"operation": "offer",
|
|
10
|
+
"key": "your public workspace UUID",
|
|
11
|
+
"device": "a random browser-session identifier"
|
|
12
|
+
}</code></pre>
|
|
13
|
+
<div class="table-wrap"><table><thead><tr><th scope="col">Data</th><th scope="col">Use and handling</th></tr></thead><tbody>
|
|
14
|
+
<tr><th scope="row">Workspace key</th><td>Selects your workspace. Public configuration, not an account password. The server checks the website origin against that workspace.</td></tr>
|
|
15
|
+
<tr><th scope="row">Session identifier</th><td>Random 256-bit value stored in <code>sessionStorage</code> under <code>sightspool-widget-device:WORKSPACE_KEY</code>. Links an invitation to that browser session. It is not a fingerprint. Browser session restore or a copied tab can extend its lifetime. If storage is blocked, it stays in memory.</td></tr>
|
|
16
|
+
<tr><th scope="row">Signed-in state</th><td><code>identify(userId)</code> retains only whether a nonempty ID is present. The ID itself is neither stored nor transmitted by the SDK. This is a client-side display signal, not proof of a participant’s identity.</td></tr>
|
|
17
|
+
<tr><th scope="row">Network information</th><td>Hosting infrastructure receives ordinary request metadata, including IP address, time and browser headers. The website’s <code>Origin</code> is required. SDK offer requests omit cookies and suppress <code>Referer</code>. The generated script tag uses anonymous cross-origin loading and a no-referrer policy; same-origin script loads may still include that origin’s cookies. This is not a claim of complete anonymity.</td></tr>
|
|
18
|
+
<tr><th scope="row">Page contents and activity</th><td>The research runtime does not collect page text, fields, clicks, URLs, browsing history, credentials or analytics events. Like other page scripts it technically runs with page privileges; these are inspectable implementation limits, not a browser sandbox guarantee.</td></tr>
|
|
19
|
+
<tr><th scope="row">Server contact receipt</th><td>A valid request updates the workspace’s last installation-contact time. An available offer binds a hash of the session identifier to a signed, expiring invitation. Connection alone does not prove an interview occurred.</td></tr>
|
|
20
|
+
</tbody></table></div>
|
|
21
|
+
<p>The SDK writes no cookies or localStorage. Sightspool’s own website and signed-in app have separate authentication and analytics behavior described in the <a href="https://www.sightspool.com/privacy">privacy notice</a>.</p></section>
|
|
22
|
+
<section id="interviews"><h2>After someone chooses to participate</h2><p>Clicking the launcher opens Sightspool’s interview experience. The participant reviews eligibility, the thank-you offer and research/recording consent before starting. Recording, transcription and analysis are separate server features; they are not hidden inside the invitation SDK.</p>
|
|
23
|
+
<ul><li><strong>Collected during an interview:</strong> consent and eligibility receipts, interview audio when agreed, transcript, operational call records and derived findings.</li><li><strong>Access:</strong> research belongs to the client workspace. Its authorized members and the service processes needed to deliver the interview have access under the product’s access controls.</li><li><strong>Retention and withdrawal:</strong> the approved interview specifies its retention period, up to 30 days in the current User Hours flow. Withdrawal or expiry triggers removal of audio, transcripts and derived findings. Removing the SDK only stops future recruitment; it does not delete interview records, account records or provider logs.</li><li><strong>Providers:</strong> hosting/storage use Vercel and Supabase; the voice path uses LiveKit, with Twilio for configured phone handoff; transcription/analysis use OpenAI. The exact path depends on the enabled features. See the privacy notice for wider product providers and contact us for a deployment-specific processing inventory.</li><li><strong>AI use:</strong> interview material is processed to transcribe and analyse the research. The privacy notice states that workspace content and research responses are not used to train models.</li></ul>
|
|
24
|
+
<p class="note">The browser SDK source cannot establish hosting regions, provider log retention or backup deletion times. A verified deployment-specific statement covering those details and the full provider inventory is still being prepared. If your organization requires it for installation approval, contact <a href="mailto:privacy@sightspool.com">privacy@sightspool.com</a> before proceeding.</p></section>
|
|
25
|
+
<section id="verify"><h2>Inspect the exact release</h2><p>Version numbers identify a release; hashes identify its bytes. The script URL includes both, and an integrity attribute lets the browser reject a modified bundle. The manifest and source snapshot are available beside the bundle.</p>
|
|
26
|
+
<div class="card" id="release-details"><p id="release-status" role="status">Loading the release manifest…</p><dl id="release-metrics"></dl><p id="release-links"></p><details><summary>Integrity and source details</summary><pre id="release-json"></pre></details></div>
|
|
27
|
+
<p><a class="button" href="demo.html">Try the SDK and inspect its requests</a></p>
|
|
28
|
+
<p>The demo runs this built SDK against simulated offer responses. It displays request fields and lifecycle state, creates no interview and requests no microphone. It does not certify production infrastructure or replace an independent review.</p>
|
|
29
|
+
<h3>npm installation</h3><pre><code>npm install --save-exact @sightspool/sdk@0.4.0</code></pre><p>Commit your lockfile and review upgrades. Our release workflow uses npm trusted publishing with provenance and verifies the tag against the package version. Check the package’s npm provenance record to verify the publishing workflow. Provenance links source and build history; it does not certify that code is secure. <a href="https://docs.npmjs.com/viewing-package-provenance/">How to check npm provenance</a>.</p>
|
|
30
|
+
<h3>Script installation and self-hosting</h3><p>Go live supplies your workspace key, exact script URL, SHA-384 integrity, anonymous cross-origin loading and no-referrer policy. Keep them together. Avoid the mutable <code>/sdk.global.js</code> alias when you need controlled upgrades.</p><p>You can download and serve the reviewed bundle yourself. Keep its hash unchanged and explicitly set <code>data-sightspool-endpoint</code> to your Sightspool workspace host; otherwise script auto-init uses the bundle’s host. Self-hosting the bundle does not self-host the interview service.</p>
|
|
31
|
+
<details><summary>Verify the downloaded bundle yourself</summary><pre><code>openssl dgst -sha384 -binary sdk.global.js | openssl base64 -A</code></pre><p>Compare the result with the manifest’s integrity value after its <code>sha384-</code> prefix. Review the source snapshot as well: a hash supplied beside a file establishes consistency, not independent trust in the publisher.</p></details></section>
|
|
32
|
+
<section id="controls"><h2>You control where it runs</h2><ul><li><strong>Audience:</strong> choose <code>all_visitors</code> or <code>signed_in</code>. There is no implicit default. Match this to your approved research plan.</li><li><strong>Pause:</strong> <code>Sightspool.pause()</code> removes the invitation and stops offer requests. <code>resume()</code> resumes for the selected audience.</li><li><strong>Remove:</strong> call <code>destroy()</code> to remove the launcher, timers and listeners, then remove the loader/component and package. An already opened interview is not silently ended. You may remove the specific sessionStorage key above if you also want to clear the SDK’s local session identifier.</li><li><strong>Rollback:</strong> retain the previously reviewed bundle and matching hash or restore the previous lockfile. Test compatibility with the current interview service; do not roll back to the retired 0.3.x capture API.</li></ul>
|
|
33
|
+
<p>Offer polling runs every 15 seconds while the page is visible and the selected audience is eligible, plus initialization and explicit lifecycle changes. One current request is in flight, with a ten-second timeout. Pausing, identity changes, hiding the tab and destruction invalidate late responses. Failures remove the invitation and are handled without throwing into normal host-page interaction.</p><p>Bundle and gzip sizes above are measured build artifacts, not a promise about page speed. Test your app’s responsiveness, layout, network use, slow loads, blocked storage and logout on your supported browsers. The SDK uses inline launcher styling; validate it against your CSP. Preserve your CSP and nonce rules—do not disable them to make installation pass.</p></section>
|
|
34
|
+
<section id="security"><h2>Security questions and review status</h2><p><strong>No independent security audit is claimed for research SDK 0.4.0.</strong> Automated checks cover request fields, identity exclusion, stale responses, cleanup and artifact integrity. Their scope is narrower than a security assessment of the SDK and interview backend together.</p><p>Report a suspected vulnerability privately to <a href="mailto:security@sightspool.com">security@sightspool.com</a>. Include the version, affected feature and a minimal reproduction without customer data. See <a href="https://www.sightspool.com/.well-known/security.txt">security.txt</a> for the existing disclosure contact and scope. Data-access and deletion questions go to <a href="mailto:privacy@sightspool.com">privacy@sightspool.com</a>.</p></section>
|
|
35
|
+
<footer>Apache-2.0 · <a href="https://github.com/sightspool/sdk">SDK source</a> · <a href="https://www.sightspool.com/privacy">Privacy notice</a> · <a href="index.html">Installation guide</a></footer>
|
|
36
|
+
</main></body></html>
|
package/dist/chunk-4PADVIIO.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// src/privacy.ts
|
|
2
|
-
var EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
3
|
-
var LONG_DIGITS_RE = /\d{6,}/g;
|
|
4
|
-
var MAX_LABEL_LEN = 280;
|
|
5
|
-
function redactText(input) {
|
|
6
|
-
if (!input) return "";
|
|
7
|
-
try {
|
|
8
|
-
return String(input).replace(EMAIL_RE, "\u2039email\u203A").replace(LONG_DIGITS_RE, "\u2039num\u203A").replace(/\s+/g, " ").trim().slice(0, MAX_LABEL_LEN);
|
|
9
|
-
} catch (e) {
|
|
10
|
-
return "";
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
var ATTACH_DISCLOSURE = "Session and account attached automatically.";
|
|
14
|
-
var REDACTED = "\u2039redacted\u203A";
|
|
15
|
-
var IGNORE_ATTR = "data-sightspool-ignore";
|
|
16
|
-
function safeClosest(el, selector) {
|
|
17
|
-
try {
|
|
18
|
-
return typeof el.closest === "function" && el.closest(selector) != null;
|
|
19
|
-
} catch (e) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function isIgnoredElement(el, blockSelectors = []) {
|
|
24
|
-
if (!el || typeof el.closest !== "function") return false;
|
|
25
|
-
if (safeClosest(el, `[${IGNORE_ATTR}]`)) return true;
|
|
26
|
-
for (const sel of blockSelectors) {
|
|
27
|
-
if (sel && safeClosest(el, sel)) return true;
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
function matchesRedactSelector(el, redactSelectors = []) {
|
|
32
|
-
if (!el || typeof el.closest !== "function") return false;
|
|
33
|
-
for (const sel of redactSelectors) {
|
|
34
|
-
if (sel && safeClosest(el, sel)) return true;
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export { ATTACH_DISCLOSURE, REDACTED, isIgnoredElement, matchesRedactSelector, redactText };
|
|
40
|
-
//# sourceMappingURL=chunk-4PADVIIO.js.map
|
|
41
|
-
//# sourceMappingURL=chunk-4PADVIIO.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/privacy.ts"],"names":[],"mappings":";AAIA,IAAM,QAAA,GAAW,iDAAA;AACjB,IAAM,cAAA,GAAiB,SAAA;AAEhB,IAAM,aAAA,GAAgB,GAAA;AAMtB,SAAS,WAAW,KAAA,EAA0C;AACnE,EAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AACnB,EAAA,IAAI;AACF,IAAA,OAAO,OAAO,KAAK,CAAA,CAChB,QAAQ,QAAA,EAAU,mBAAS,EAC3B,OAAA,CAAQ,cAAA,EAAgB,iBAAO,CAAA,CAC/B,OAAA,CAAQ,QAAQ,GAAG,CAAA,CACnB,MAAK,CACL,KAAA,CAAM,GAAG,aAAa,CAAA;AAAA,EAC3B,CAAA,CAAA,OAAQ,CAAA,EAAA;AACN,IAAA,OAAO,EAAA;AAAA,EACT;AACF;AAQO,IAAM,iBAAA,GAAoB;AAG1B,IAAM,QAAA,GAAW;AAGjB,IAAM,WAAA,GAAc,wBAAA;AAK3B,SAAS,WAAA,CAAY,IAAe,QAAA,EAA2B;AAC7D,EAAA,IAAI;AACF,IAAA,OAAO,OAAO,EAAA,CAAG,OAAA,KAAY,cAAc,EAAA,CAAG,OAAA,CAAQ,QAAQ,CAAA,IAAK,IAAA;AAAA,EACrE,CAAA,CAAA,OAAQ,CAAA,EAAA;AAIN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAOO,SAAS,gBAAA,CACd,EAAA,EACA,cAAA,GAAoC,EAAC,EAC5B;AACT,EAAA,IAAI,CAAC,EAAA,IAAM,OAAO,EAAA,CAAG,OAAA,KAAY,YAAY,OAAO,KAAA;AACpD,EAAA,IAAI,YAAY,EAAA,EAAI,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA,CAAG,GAAG,OAAO,IAAA;AAChD,EAAA,KAAA,MAAW,OAAO,cAAA,EAAgB;AAChC,IAAA,IAAI,GAAA,IAAO,WAAA,CAAY,EAAA,EAAI,GAAG,GAAG,OAAO,IAAA;AAAA,EAC1C;AACA,EAAA,OAAO,KAAA;AACT;AAOO,SAAS,qBAAA,CACd,EAAA,EACA,eAAA,GAAqC,EAAC,EAC7B;AACT,EAAA,IAAI,CAAC,EAAA,IAAM,OAAO,EAAA,CAAG,OAAA,KAAY,YAAY,OAAO,KAAA;AACpD,EAAA,KAAA,MAAW,OAAO,eAAA,EAAiB;AACjC,IAAA,IAAI,GAAA,IAAO,WAAA,CAAY,EAAA,EAAI,GAAG,GAAG,OAAO,IAAA;AAAA,EAC1C;AACA,EAAA,OAAO,KAAA;AACT","file":"chunk-4PADVIIO.js","sourcesContent":["// Redaction — mask sensitive text before it ever leaves the page (GOV-3). The\n// pure string helpers are unit-tested; the DOM-querying part (selector masking)\n// lives in capture.ts and calls in here.\n\nconst EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g;\nconst LONG_DIGITS_RE = /\\d{6,}/g; // card / account-number-ish runs\n\nexport const MAX_LABEL_LEN = 280;\n\n/**\n * Mask PII-ish substrings in a captured label/verbatim and clamp length.\n * Conservative: emails and long digit runs become placeholders. Never throws.\n */\nexport function redactText(input: string | null | undefined): string {\n if (!input) return \"\";\n try {\n return String(input)\n .replace(EMAIL_RE, \"‹email›\")\n .replace(LONG_DIGITS_RE, \"‹num›\")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, MAX_LABEL_LEN);\n } catch {\n return \"\";\n }\n}\n\n/** Should a captured element's value be dropped entirely (not just masked)? */\nexport function isSensitiveInput(type: string | null | undefined): boolean {\n const t = (type ?? \"\").toLowerCase();\n return t === \"password\" || t === \"email\" || t === \"tel\" || t === \"creditcard\";\n}\n\nexport const ATTACH_DISCLOSURE = \"Session and account attached automatically.\";\n\n/** Placeholder substituted for the text of a `redact`-matched element (GOV-3). */\nexport const REDACTED = \"‹redacted›\";\n\n/** The per-element opt-out attribute: a click/input inside `[data-sightspool-ignore]` is dropped entirely. */\nexport const IGNORE_ATTR = \"data-sightspool-ignore\";\n\n/** Anything with a `.closest(selector)` — the bit of Element we depend on (kept DOM-free for unit tests). */\ntype Closeable = { closest?: (selectors: string) => unknown };\n\nfunction safeClosest(el: Closeable, selector: string): boolean {\n try {\n return typeof el.closest === \"function\" && el.closest(selector) != null;\n } catch {\n // An invalid selector (host misconfig) must never throw into capture — and must\n // never *accidentally capture* what was meant to be excluded: treat as no-match\n // for that one selector and let the others decide.\n return false;\n }\n}\n\n/**\n * Should this element's events be dropped entirely (not just masked)? True when the\n * element — or any ancestor — carries `data-sightspool-ignore` or matches one of the\n * configured `block` selectors. The host's hard privacy opt-out (GOV-3).\n */\nexport function isIgnoredElement(\n el: Closeable | null | undefined,\n blockSelectors: readonly string[] = [],\n): boolean {\n if (!el || typeof el.closest !== \"function\") return false;\n if (safeClosest(el, `[${IGNORE_ATTR}]`)) return true;\n for (const sel of blockSelectors) {\n if (sel && safeClosest(el, sel)) return true;\n }\n return false;\n}\n\n/**\n * Should this element's captured text be masked wholesale? True when the element —\n * or any ancestor — matches one of the configured `redact` selectors. (The event is\n * still recorded; only its label becomes {@link REDACTED}.)\n */\nexport function matchesRedactSelector(\n el: Closeable | null | undefined,\n redactSelectors: readonly string[] = [],\n): boolean {\n if (!el || typeof el.closest !== \"function\") return false;\n for (const sel of redactSelectors) {\n if (sel && safeClosest(el, sel)) return true;\n }\n return false;\n}\n"]}
|
package/dist/probe-BNGMQQ5C.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
// src/probe.ts
|
|
2
|
-
var FLOATING_STYLE = `
|
|
3
|
-
:host { all: initial; }
|
|
4
|
-
.wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
|
|
5
|
-
width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
|
|
6
|
-
.card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;
|
|
7
|
-
border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
|
|
8
|
-
.probe { font: inherit; font-size: 13px; cursor: pointer; display: block; width: 100%;
|
|
9
|
-
text-align: left; border: 1px dashed #a1a1aa; border-radius: 8px; padding: 10px 12px;
|
|
10
|
-
background: #fafafa; color: #3f3f46; }
|
|
11
|
-
.probe:hover { border-color: #18181b; color: #18181b; }
|
|
12
|
-
.x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
|
|
13
|
-
color: #a1a1aa; padding: 2px 6px; cursor: pointer; }
|
|
14
|
-
.done { font-size: 13px; color: #18181b; margin: 0; line-height: 1.4; }
|
|
15
|
-
`;
|
|
16
|
-
var INLINE_STYLE = `
|
|
17
|
-
:host { all: initial; display: inline-block; max-width: 100%; }
|
|
18
|
-
.probe { font: inherit; font-family: system-ui, -apple-system, sans-serif; font-size: 13px;
|
|
19
|
-
cursor: pointer; display: inline-block; text-align: left; border: 1px dashed #a1a1aa;
|
|
20
|
-
border-radius: 8px; padding: 8px 12px; background: transparent; color: #52525b; }
|
|
21
|
-
.probe:hover { border-color: #18181b; color: #18181b; }
|
|
22
|
-
.done { font-family: system-ui, -apple-system, sans-serif; font-size: 13px; color: #52525b;
|
|
23
|
-
margin: 0; line-height: 1.4; border: 1px dashed #d4d4d8; border-radius: 8px; padding: 8px 12px; }
|
|
24
|
-
`;
|
|
25
|
-
var DISCLOSURE_MS = 3200;
|
|
26
|
-
function findSlot(slot) {
|
|
27
|
-
try {
|
|
28
|
-
return document.querySelector(`[data-sightspool-slot="${slot}"]`);
|
|
29
|
-
} catch (e) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function showProbe(config) {
|
|
34
|
-
return new Promise((resolve) => {
|
|
35
|
-
if (typeof document === "undefined") {
|
|
36
|
-
resolve({ dismissed: true, mounted: false });
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const anchor = config.slot ? findSlot(config.slot) : null;
|
|
40
|
-
if (config.slot && !anchor) {
|
|
41
|
-
resolve({ mounted: false });
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
let settled = false;
|
|
45
|
-
const inline = !!anchor;
|
|
46
|
-
const host = document.createElement(inline ? "span" : "div");
|
|
47
|
-
const shadow = host.attachShadow({ mode: "open" });
|
|
48
|
-
function teardown() {
|
|
49
|
-
try {
|
|
50
|
-
host.remove();
|
|
51
|
-
} catch (e) {
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function finish(result, disclose) {
|
|
55
|
-
if (settled) return;
|
|
56
|
-
settled = true;
|
|
57
|
-
resolve(result);
|
|
58
|
-
if (disclose) {
|
|
59
|
-
disclosure();
|
|
60
|
-
setTimeout(teardown, DISCLOSURE_MS);
|
|
61
|
-
} else {
|
|
62
|
-
teardown();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const style = document.createElement("style");
|
|
66
|
-
style.textContent = inline ? INLINE_STYLE : FLOATING_STYLE;
|
|
67
|
-
shadow.appendChild(style);
|
|
68
|
-
const wrap = document.createElement(inline ? "span" : "div");
|
|
69
|
-
if (!inline) wrap.className = "wrap";
|
|
70
|
-
shadow.appendChild(wrap);
|
|
71
|
-
function container() {
|
|
72
|
-
wrap.innerHTML = "";
|
|
73
|
-
if (inline) return wrap;
|
|
74
|
-
const c = document.createElement("div");
|
|
75
|
-
c.className = "card";
|
|
76
|
-
wrap.appendChild(c);
|
|
77
|
-
return c;
|
|
78
|
-
}
|
|
79
|
-
function probe() {
|
|
80
|
-
const c = container();
|
|
81
|
-
if (!inline) {
|
|
82
|
-
const x = document.createElement("button");
|
|
83
|
-
x.className = "x";
|
|
84
|
-
x.textContent = "\xD7";
|
|
85
|
-
x.setAttribute("aria-label", "Dismiss");
|
|
86
|
-
x.onclick = () => finish({ dismissed: true, mounted: true }, false);
|
|
87
|
-
c.appendChild(x);
|
|
88
|
-
}
|
|
89
|
-
const b = document.createElement("button");
|
|
90
|
-
b.className = "probe";
|
|
91
|
-
b.textContent = config.label;
|
|
92
|
-
b.onclick = () => finish({ voted: true, mounted: true }, true);
|
|
93
|
-
c.appendChild(b);
|
|
94
|
-
}
|
|
95
|
-
function disclosure() {
|
|
96
|
-
const c = container();
|
|
97
|
-
const p = document.createElement("p");
|
|
98
|
-
p.className = "done";
|
|
99
|
-
p.textContent = config.disclosure;
|
|
100
|
-
c.appendChild(p);
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
(anchor != null ? anchor : document.body).appendChild(host);
|
|
104
|
-
probe();
|
|
105
|
-
} catch (e) {
|
|
106
|
-
finish({ dismissed: true, mounted: false }, false);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export { showProbe };
|
|
112
|
-
//# sourceMappingURL=probe-BNGMQQ5C.js.map
|
|
113
|
-
//# sourceMappingURL=probe-BNGMQQ5C.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/probe.ts"],"names":[],"mappings":"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","file":"probe-BNGMQQ5C.js","sourcesContent":["// The demand-probe widget — the honest painted door, client half. Renders a\n// human-approved probe served by /api/sdk/serve: a dashed-border button\n// standing in for a feature that DOESN'T exist yet. Clicking is a vote; the\n// widget then shows the server's fixed disclosure (\"this doesn't exist yet —\n// your click was counted as a vote\"), so the pattern is never a deceptive\n// fake door. Same shadow-root + lazy-import discipline as survey.ts.\n//\n// Two placements:\n// floating (default) — the survey-style card, bottom-right; zero integration.\n// slot — the button mounts INSIDE the client's own UI, but only where the\n// client explicitly invited it: an element carrying\n// data-sightspool-slot=\"<slot>\". We only ever append into that anchor\n// (own host node + shadow root — no sibling injection, no layout edits);\n// if the anchor isn't on the page, we mount NOTHING and report it, so the\n// caller can restore its fatigue counters.\n\nexport type ProbeConfig = {\n /** The feature as the user would want it — the dashed button's copy. */\n label: string;\n /** The fixed post-vote honesty line, injected server-side. Required: the\n * widget refuses to render without it (see normalizeServed). */\n disclosure: string;\n /** Optional inline anchor: mount inside [data-sightspool-slot=\"<slot>\"]. */\n slot?: string;\n};\n\nexport type ProbeResult = {\n voted?: boolean;\n dismissed?: boolean;\n /** false = the declared slot wasn't on this page, nothing rendered. */\n mounted: boolean;\n};\n\nconst FLOATING_STYLE = `\n:host { all: initial; }\n.wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;\n width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }\n.card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;\n border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }\n.probe { font: inherit; font-size: 13px; cursor: pointer; display: block; width: 100%;\n text-align: left; border: 1px dashed #a1a1aa; border-radius: 8px; padding: 10px 12px;\n background: #fafafa; color: #3f3f46; }\n.probe:hover { border-color: #18181b; color: #18181b; }\n.x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;\n color: #a1a1aa; padding: 2px 6px; cursor: pointer; }\n.done { font-size: 13px; color: #18181b; margin: 0; line-height: 1.4; }\n`;\n\n// Inline: no fixed positioning, no card chrome — just the dashed affordance,\n// sized by its content, so the host layout stays the host's.\nconst INLINE_STYLE = `\n:host { all: initial; display: inline-block; max-width: 100%; }\n.probe { font: inherit; font-family: system-ui, -apple-system, sans-serif; font-size: 13px;\n cursor: pointer; display: inline-block; text-align: left; border: 1px dashed #a1a1aa;\n border-radius: 8px; padding: 8px 12px; background: transparent; color: #52525b; }\n.probe:hover { border-color: #18181b; color: #18181b; }\n.done { font-family: system-ui, -apple-system, sans-serif; font-size: 13px; color: #52525b;\n margin: 0; line-height: 1.4; border: 1px dashed #d4d4d8; border-radius: 8px; padding: 8px 12px; }\n`;\n\nconst DISCLOSURE_MS = 3200; // long enough to read the honesty line, then close\n\nfunction findSlot(slot: string): Element | null {\n try {\n // The slot token is validated upstream ([a-z0-9_-]), so it's quote-safe.\n return document.querySelector(`[data-sightspool-slot=\"${slot}\"]`);\n } catch {\n return null;\n }\n}\n\nexport function showProbe(config: ProbeConfig): Promise<ProbeResult> {\n return new Promise((resolve) => {\n if (typeof document === \"undefined\") {\n resolve({ dismissed: true, mounted: false });\n return;\n }\n\n const anchor = config.slot ? findSlot(config.slot) : null;\n if (config.slot && !anchor) {\n // The client declared a placement and this page doesn't have it —\n // render nothing (never fall back to floating: the probe shows where\n // the client intended, or nowhere).\n resolve({ mounted: false });\n return;\n }\n\n let settled = false;\n const inline = !!anchor;\n const host = document.createElement(inline ? \"span\" : \"div\");\n const shadow = host.attachShadow({ mode: \"open\" });\n\n function teardown() {\n try {\n host.remove();\n } catch {\n /* swallow */\n }\n }\n\n function finish(result: ProbeResult, disclose: boolean) {\n if (settled) return;\n settled = true;\n resolve(result);\n if (disclose) {\n disclosure();\n setTimeout(teardown, DISCLOSURE_MS);\n } else {\n teardown();\n }\n }\n\n const style = document.createElement(\"style\");\n style.textContent = inline ? INLINE_STYLE : FLOATING_STYLE;\n shadow.appendChild(style);\n\n const wrap = document.createElement(inline ? \"span\" : \"div\");\n if (!inline) wrap.className = \"wrap\";\n shadow.appendChild(wrap);\n\n function container(): HTMLElement {\n wrap.innerHTML = \"\";\n if (inline) return wrap as HTMLElement;\n const c = document.createElement(\"div\");\n c.className = \"card\";\n wrap.appendChild(c);\n return c;\n }\n\n function probe() {\n const c = container();\n if (!inline) {\n const x = document.createElement(\"button\");\n x.className = \"x\";\n x.textContent = \"×\";\n x.setAttribute(\"aria-label\", \"Dismiss\");\n x.onclick = () => finish({ dismissed: true, mounted: true }, false);\n c.appendChild(x);\n }\n const b = document.createElement(\"button\");\n b.className = \"probe\";\n b.textContent = config.label;\n b.onclick = () => finish({ voted: true, mounted: true }, true);\n c.appendChild(b);\n }\n\n // The truth-telling moment — the only thing separating this from a fake\n // door. Always the server's fixed copy, shown after every vote.\n function disclosure() {\n const c = container();\n const p = document.createElement(\"p\");\n p.className = \"done\";\n p.textContent = config.disclosure;\n c.appendChild(p);\n }\n\n try {\n (anchor ?? document.body).appendChild(host);\n probe();\n } catch {\n finish({ dismissed: true, mounted: false }, false);\n }\n });\n}\n"]}
|
package/dist/prompt-GDFYC75D.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { ATTACH_DISCLOSURE } from './chunk-4PADVIIO.js';
|
|
2
|
-
|
|
3
|
-
// src/prompt.ts
|
|
4
|
-
var STYLE = `
|
|
5
|
-
:host { all: initial; }
|
|
6
|
-
.wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
|
|
7
|
-
width: 320px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
|
|
8
|
-
.card { background: #fff; color: #18181b; border: 1px solid #e4e4e7; border-radius: 12px;
|
|
9
|
-
box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
|
|
10
|
-
.q { font-size: 14px; font-weight: 600; margin: 0 0 12px; line-height: 1.35; }
|
|
11
|
-
.row { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
12
|
-
button { font: inherit; font-size: 13px; cursor: pointer; border-radius: 8px; padding: 8px 12px;
|
|
13
|
-
border: 1px solid #e4e4e7; background: #fafafa; color: #18181b; }
|
|
14
|
-
button:hover { background: #f4f4f5; }
|
|
15
|
-
button.primary { background: #18181b; color: #fff; border-color: #18181b; }
|
|
16
|
-
.cand { display: block; width: 100%; text-align: left; margin-bottom: 6px; }
|
|
17
|
-
.disc { font-size: 11px; color: #71717a; margin: 12px 0 0; }
|
|
18
|
-
.x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
|
|
19
|
-
color: #a1a1aa; padding: 2px 6px; }
|
|
20
|
-
input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; padding: 8px 10px;
|
|
21
|
-
border: 1px solid #e4e4e7; border-radius: 8px; margin-bottom: 8px; }
|
|
22
|
-
`;
|
|
23
|
-
function showPrompt(opts) {
|
|
24
|
-
return new Promise((resolve) => {
|
|
25
|
-
if (typeof document === "undefined") {
|
|
26
|
-
resolve({ answer: "dismissed" });
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
let settled = false;
|
|
30
|
-
const host = document.createElement("div");
|
|
31
|
-
const shadow = host.attachShadow({ mode: "open" });
|
|
32
|
-
function finish(result) {
|
|
33
|
-
if (settled) return;
|
|
34
|
-
settled = true;
|
|
35
|
-
try {
|
|
36
|
-
host.remove();
|
|
37
|
-
} catch (e) {
|
|
38
|
-
}
|
|
39
|
-
resolve(result);
|
|
40
|
-
}
|
|
41
|
-
const style = document.createElement("style");
|
|
42
|
-
style.textContent = STYLE;
|
|
43
|
-
shadow.appendChild(style);
|
|
44
|
-
const wrap = document.createElement("div");
|
|
45
|
-
wrap.className = "wrap";
|
|
46
|
-
shadow.appendChild(wrap);
|
|
47
|
-
function render(node) {
|
|
48
|
-
wrap.innerHTML = "";
|
|
49
|
-
const card = document.createElement("div");
|
|
50
|
-
card.className = "card";
|
|
51
|
-
card.style.position = "relative";
|
|
52
|
-
const x = document.createElement("button");
|
|
53
|
-
x.className = "x";
|
|
54
|
-
x.textContent = "\xD7";
|
|
55
|
-
x.setAttribute("aria-label", "Dismiss");
|
|
56
|
-
x.onclick = () => finish({ answer: "dismissed" });
|
|
57
|
-
card.appendChild(x);
|
|
58
|
-
card.appendChild(node);
|
|
59
|
-
const disc = document.createElement("p");
|
|
60
|
-
disc.className = "disc";
|
|
61
|
-
disc.textContent = ATTACH_DISCLOSURE;
|
|
62
|
-
card.appendChild(disc);
|
|
63
|
-
wrap.appendChild(card);
|
|
64
|
-
}
|
|
65
|
-
function stateA() {
|
|
66
|
-
const node = document.createElement("div");
|
|
67
|
-
const q = document.createElement("p");
|
|
68
|
-
q.className = "q";
|
|
69
|
-
q.textContent = "Were you able to do what you came here to do today?";
|
|
70
|
-
node.appendChild(q);
|
|
71
|
-
const row = document.createElement("div");
|
|
72
|
-
row.className = "row";
|
|
73
|
-
const yes = document.createElement("button");
|
|
74
|
-
yes.className = "primary";
|
|
75
|
-
yes.textContent = "Yes";
|
|
76
|
-
yes.onclick = () => finish({ answer: "yes" });
|
|
77
|
-
const no = document.createElement("button");
|
|
78
|
-
no.textContent = "Not really";
|
|
79
|
-
no.onclick = () => stateB();
|
|
80
|
-
row.append(yes, no);
|
|
81
|
-
node.appendChild(row);
|
|
82
|
-
render(node);
|
|
83
|
-
}
|
|
84
|
-
function stateB() {
|
|
85
|
-
const node = document.createElement("div");
|
|
86
|
-
const q = document.createElement("p");
|
|
87
|
-
q.className = "q";
|
|
88
|
-
q.textContent = "What were you trying to do?";
|
|
89
|
-
node.appendChild(q);
|
|
90
|
-
for (const c of opts.candidates.slice(0, 4)) {
|
|
91
|
-
const b = document.createElement("button");
|
|
92
|
-
b.className = "cand";
|
|
93
|
-
b.textContent = c;
|
|
94
|
-
b.onclick = () => finish({ answer: "not_really", intent: c });
|
|
95
|
-
node.appendChild(b);
|
|
96
|
-
}
|
|
97
|
-
const se = document.createElement("button");
|
|
98
|
-
se.className = "cand";
|
|
99
|
-
se.textContent = "Something else\u2026";
|
|
100
|
-
se.onclick = () => stateText();
|
|
101
|
-
node.appendChild(se);
|
|
102
|
-
render(node);
|
|
103
|
-
}
|
|
104
|
-
function stateText() {
|
|
105
|
-
const node = document.createElement("div");
|
|
106
|
-
const q = document.createElement("p");
|
|
107
|
-
q.className = "q";
|
|
108
|
-
q.textContent = "What were you trying to do?";
|
|
109
|
-
node.appendChild(q);
|
|
110
|
-
const input = document.createElement("input");
|
|
111
|
-
input.type = "text";
|
|
112
|
-
input.placeholder = "In your own words\u2026";
|
|
113
|
-
node.appendChild(input);
|
|
114
|
-
const row = document.createElement("div");
|
|
115
|
-
row.className = "row";
|
|
116
|
-
const submit = document.createElement("button");
|
|
117
|
-
submit.className = "primary";
|
|
118
|
-
submit.textContent = "Send";
|
|
119
|
-
submit.onclick = () => finish({ answer: "not_really", verbatim: input.value.trim() || void 0 });
|
|
120
|
-
row.appendChild(submit);
|
|
121
|
-
node.appendChild(row);
|
|
122
|
-
render(node);
|
|
123
|
-
try {
|
|
124
|
-
input.focus();
|
|
125
|
-
} catch (e) {
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
document.body.appendChild(host);
|
|
130
|
-
stateA();
|
|
131
|
-
} catch (e) {
|
|
132
|
-
finish({ answer: "dismissed" });
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export { showPrompt };
|
|
138
|
-
//# sourceMappingURL=prompt-GDFYC75D.js.map
|
|
139
|
-
//# sourceMappingURL=prompt-GDFYC75D.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/prompt.ts"],"names":[],"mappings":"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","file":"prompt-GDFYC75D.js","sourcesContent":["// The prompt UI (FR-5/6, the §16.2 blend) — rendered in a shadow root so the host\n// app's CSS can't style-break it and it leaks zero styles back. This module is\n// dynamically imported by index.ts only when a trigger fires, so it splits into a\n// lazily-loaded chunk and adds nothing to the host bundle until needed.\n//\n// State A — \"Were you able to do what you came here to do today?\" [Yes][Not really]\n// State B — (after \"Not really\") top candidate goals + an always-present\n// \"Something else\" → optional free text only after that.\n\nimport type { SignalAnswer } from \"./types\";\nimport { ATTACH_DISCLOSURE } from \"./privacy\";\n\nexport type PromptResult = {\n answer: SignalAnswer;\n intent?: string; // a tapped candidate\n verbatim?: string; // free text from \"Something else\"\n};\n\nexport type PromptOptions = {\n /** Locally-derived candidate goals (FR-5 State B). May be empty → generic ask. */\n candidates: string[];\n};\n\nconst STYLE = `\n:host { all: initial; }\n.wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;\n width: 320px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }\n.card { background: #fff; color: #18181b; border: 1px solid #e4e4e7; border-radius: 12px;\n box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }\n.q { font-size: 14px; font-weight: 600; margin: 0 0 12px; line-height: 1.35; }\n.row { display: flex; gap: 8px; flex-wrap: wrap; }\nbutton { font: inherit; font-size: 13px; cursor: pointer; border-radius: 8px; padding: 8px 12px;\n border: 1px solid #e4e4e7; background: #fafafa; color: #18181b; }\nbutton:hover { background: #f4f4f5; }\nbutton.primary { background: #18181b; color: #fff; border-color: #18181b; }\n.cand { display: block; width: 100%; text-align: left; margin-bottom: 6px; }\n.disc { font-size: 11px; color: #71717a; margin: 12px 0 0; }\n.x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;\n color: #a1a1aa; padding: 2px 6px; }\ninput { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; padding: 8px 10px;\n border: 1px solid #e4e4e7; border-radius: 8px; margin-bottom: 8px; }\n`;\n\nexport function showPrompt(opts: PromptOptions): Promise<PromptResult> {\n return new Promise((resolve) => {\n if (typeof document === \"undefined\") {\n resolve({ answer: \"dismissed\" });\n return;\n }\n\n let settled = false;\n const host = document.createElement(\"div\");\n const shadow = host.attachShadow({ mode: \"open\" });\n\n function finish(result: PromptResult) {\n if (settled) return;\n settled = true;\n try {\n host.remove();\n } catch {\n /* swallow */\n }\n resolve(result);\n }\n\n const style = document.createElement(\"style\");\n style.textContent = STYLE;\n shadow.appendChild(style);\n\n const wrap = document.createElement(\"div\");\n wrap.className = \"wrap\";\n shadow.appendChild(wrap);\n\n function render(node: HTMLElement) {\n wrap.innerHTML = \"\";\n const card = document.createElement(\"div\");\n card.className = \"card\";\n card.style.position = \"relative\";\n const x = document.createElement(\"button\");\n x.className = \"x\";\n x.textContent = \"×\";\n x.setAttribute(\"aria-label\", \"Dismiss\");\n x.onclick = () => finish({ answer: \"dismissed\" });\n card.appendChild(x);\n card.appendChild(node);\n const disc = document.createElement(\"p\");\n disc.className = \"disc\";\n disc.textContent = ATTACH_DISCLOSURE;\n card.appendChild(disc);\n wrap.appendChild(card);\n }\n\n // --- State A ---\n function stateA() {\n const node = document.createElement(\"div\");\n const q = document.createElement(\"p\");\n q.className = \"q\";\n q.textContent = \"Were you able to do what you came here to do today?\";\n node.appendChild(q);\n const row = document.createElement(\"div\");\n row.className = \"row\";\n const yes = document.createElement(\"button\");\n yes.className = \"primary\";\n yes.textContent = \"Yes\";\n yes.onclick = () => finish({ answer: \"yes\" });\n const no = document.createElement(\"button\");\n no.textContent = \"Not really\";\n no.onclick = () => stateB();\n row.append(yes, no);\n node.appendChild(row);\n render(node);\n }\n\n // --- State B ---\n function stateB() {\n const node = document.createElement(\"div\");\n const q = document.createElement(\"p\");\n q.className = \"q\";\n q.textContent = \"What were you trying to do?\";\n node.appendChild(q);\n for (const c of opts.candidates.slice(0, 4)) {\n const b = document.createElement(\"button\");\n b.className = \"cand\";\n b.textContent = c;\n b.onclick = () => finish({ answer: \"not_really\", intent: c });\n node.appendChild(b);\n }\n const se = document.createElement(\"button\");\n se.className = \"cand\";\n se.textContent = \"Something else…\";\n se.onclick = () => stateText();\n node.appendChild(se);\n render(node);\n }\n\n // --- \"Something else\" free text ---\n function stateText() {\n const node = document.createElement(\"div\");\n const q = document.createElement(\"p\");\n q.className = \"q\";\n q.textContent = \"What were you trying to do?\";\n node.appendChild(q);\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.placeholder = \"In your own words…\";\n node.appendChild(input);\n const row = document.createElement(\"div\");\n row.className = \"row\";\n const submit = document.createElement(\"button\");\n submit.className = \"primary\";\n submit.textContent = \"Send\";\n submit.onclick = () =>\n finish({ answer: \"not_really\", verbatim: input.value.trim() || undefined });\n row.appendChild(submit);\n node.appendChild(row);\n render(node);\n try {\n input.focus();\n } catch {\n /* swallow */\n }\n }\n\n try {\n document.body.appendChild(host);\n stateA();\n } catch {\n finish({ answer: \"dismissed\" });\n }\n });\n}\n"]}
|
package/dist/survey-CICJQ5VI.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
// src/survey.ts
|
|
2
|
-
var STYLE = `
|
|
3
|
-
:host { all: initial; }
|
|
4
|
-
.wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
|
|
5
|
-
width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
|
|
6
|
-
.card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;
|
|
7
|
-
border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
|
|
8
|
-
.q { font-size: 14px; font-weight: 600; margin: 0 0 12px; line-height: 1.35; padding-right: 16px; }
|
|
9
|
-
.row { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
10
|
-
button { font: inherit; font-size: 13px; cursor: pointer; border-radius: 8px; padding: 8px 12px;
|
|
11
|
-
border: 1px solid #e4e4e7; background: #fafafa; color: #18181b; }
|
|
12
|
-
button:hover { background: #f4f4f5; }
|
|
13
|
-
button.primary { background: #18181b; color: #fff; border-color: #18181b; }
|
|
14
|
-
.opt { display: block; width: 100%; text-align: left; margin-bottom: 6px; }
|
|
15
|
-
.x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
|
|
16
|
-
color: #a1a1aa; padding: 2px 6px; cursor: pointer; }
|
|
17
|
-
.done { font-size: 13px; color: #18181b; margin: 0; }
|
|
18
|
-
input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; padding: 8px 10px;
|
|
19
|
-
border: 1px solid #e4e4e7; border-radius: 8px; margin-bottom: 8px; }
|
|
20
|
-
`;
|
|
21
|
-
var THANKS_MS = 1100;
|
|
22
|
-
function showSurvey(config) {
|
|
23
|
-
return new Promise((resolve) => {
|
|
24
|
-
if (typeof document === "undefined") {
|
|
25
|
-
resolve({ dismissed: true });
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
let settled = false;
|
|
29
|
-
const host = document.createElement("div");
|
|
30
|
-
const shadow = host.attachShadow({ mode: "open" });
|
|
31
|
-
function teardown() {
|
|
32
|
-
try {
|
|
33
|
-
host.remove();
|
|
34
|
-
} catch (e) {
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function finish(result, thank) {
|
|
38
|
-
if (settled) return;
|
|
39
|
-
settled = true;
|
|
40
|
-
resolve(result);
|
|
41
|
-
if (thank) {
|
|
42
|
-
thankYou();
|
|
43
|
-
setTimeout(teardown, THANKS_MS);
|
|
44
|
-
} else {
|
|
45
|
-
teardown();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const style = document.createElement("style");
|
|
49
|
-
style.textContent = STYLE;
|
|
50
|
-
shadow.appendChild(style);
|
|
51
|
-
const wrap = document.createElement("div");
|
|
52
|
-
wrap.className = "wrap";
|
|
53
|
-
shadow.appendChild(wrap);
|
|
54
|
-
function card() {
|
|
55
|
-
wrap.innerHTML = "";
|
|
56
|
-
const c = document.createElement("div");
|
|
57
|
-
c.className = "card";
|
|
58
|
-
wrap.appendChild(c);
|
|
59
|
-
return c;
|
|
60
|
-
}
|
|
61
|
-
function withClose(c) {
|
|
62
|
-
const x = document.createElement("button");
|
|
63
|
-
x.className = "x";
|
|
64
|
-
x.textContent = "\xD7";
|
|
65
|
-
x.setAttribute("aria-label", "Dismiss");
|
|
66
|
-
x.onclick = () => finish({ dismissed: true }, false);
|
|
67
|
-
c.appendChild(x);
|
|
68
|
-
}
|
|
69
|
-
function question() {
|
|
70
|
-
var _a, _b, _c;
|
|
71
|
-
const c = card();
|
|
72
|
-
withClose(c);
|
|
73
|
-
const q = document.createElement("p");
|
|
74
|
-
q.className = "q";
|
|
75
|
-
q.textContent = config.question;
|
|
76
|
-
c.appendChild(q);
|
|
77
|
-
for (const opt of ((_a = config.options) != null ? _a : []).slice(0, 6)) {
|
|
78
|
-
const b = document.createElement("button");
|
|
79
|
-
b.className = "opt";
|
|
80
|
-
b.textContent = opt;
|
|
81
|
-
b.onclick = () => finish({ choice: opt }, true);
|
|
82
|
-
c.appendChild(b);
|
|
83
|
-
}
|
|
84
|
-
if (config.allow_text) {
|
|
85
|
-
const se = document.createElement("button");
|
|
86
|
-
se.className = "opt";
|
|
87
|
-
se.textContent = ((_c = (_b = config.options) == null ? void 0 : _b.length) != null ? _c : 0) > 0 ? "Something else\u2026" : "Answer\u2026";
|
|
88
|
-
se.onclick = freeText;
|
|
89
|
-
c.appendChild(se);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
function freeText() {
|
|
93
|
-
const c = card();
|
|
94
|
-
withClose(c);
|
|
95
|
-
const q = document.createElement("p");
|
|
96
|
-
q.className = "q";
|
|
97
|
-
q.textContent = config.question;
|
|
98
|
-
c.appendChild(q);
|
|
99
|
-
const input = document.createElement("input");
|
|
100
|
-
input.type = "text";
|
|
101
|
-
input.placeholder = "In your own words\u2026";
|
|
102
|
-
c.appendChild(input);
|
|
103
|
-
const row = document.createElement("div");
|
|
104
|
-
row.className = "row";
|
|
105
|
-
const send = document.createElement("button");
|
|
106
|
-
send.className = "primary";
|
|
107
|
-
send.textContent = "Send";
|
|
108
|
-
const submit = () => {
|
|
109
|
-
const text = input.value.trim();
|
|
110
|
-
if (!text) {
|
|
111
|
-
finish({ dismissed: true }, false);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
finish({ text }, true);
|
|
115
|
-
};
|
|
116
|
-
send.onclick = submit;
|
|
117
|
-
input.onkeydown = (e) => {
|
|
118
|
-
if (e.key === "Enter") submit();
|
|
119
|
-
};
|
|
120
|
-
row.appendChild(send);
|
|
121
|
-
c.appendChild(row);
|
|
122
|
-
try {
|
|
123
|
-
input.focus();
|
|
124
|
-
} catch (e) {
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function thankYou() {
|
|
128
|
-
const c = card();
|
|
129
|
-
const p = document.createElement("p");
|
|
130
|
-
p.className = "done";
|
|
131
|
-
p.textContent = "Thanks \u2014 that helps.";
|
|
132
|
-
c.appendChild(p);
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
document.body.appendChild(host);
|
|
136
|
-
question();
|
|
137
|
-
} catch (e) {
|
|
138
|
-
finish({ dismissed: true }, false);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export { showSurvey };
|
|
144
|
-
//# sourceMappingURL=survey-CICJQ5VI.js.map
|
|
145
|
-
//# sourceMappingURL=survey-CICJQ5VI.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/survey.ts"],"names":[],"mappings":"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","file":"survey-CICJQ5VI.js","sourcesContent":["// The intervention survey widget (Wave 0005 slice B — the client half). Renders a\n// human-approved one-tap survey served by /api/sdk/serve, in a shadow root so the\n// host app's CSS can't style-break it and it leaks zero styles back. Dynamically\n// imported by index.ts only when the serve endpoint actually returns something, so\n// it splits into its own lazily-loaded chunk and adds nothing to the host bundle\n// until a survey is shown. Mirrors prompt.ts (the passive ask) deliberately.\n//\n// question + [option] [option] … (tap an option → choice)\n// + \"Something else…\" → free text (only when allow_text)\n\nexport type SurveyConfig = {\n question: string;\n options?: string[];\n allow_text?: boolean;\n};\n\nexport type SurveyResult = {\n choice?: string;\n text?: string;\n dismissed?: boolean;\n};\n\nconst STYLE = `\n:host { all: initial; }\n.wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;\n width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }\n.card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;\n border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }\n.q { font-size: 14px; font-weight: 600; margin: 0 0 12px; line-height: 1.35; padding-right: 16px; }\n.row { display: flex; gap: 8px; flex-wrap: wrap; }\nbutton { font: inherit; font-size: 13px; cursor: pointer; border-radius: 8px; padding: 8px 12px;\n border: 1px solid #e4e4e7; background: #fafafa; color: #18181b; }\nbutton:hover { background: #f4f4f5; }\nbutton.primary { background: #18181b; color: #fff; border-color: #18181b; }\n.opt { display: block; width: 100%; text-align: left; margin-bottom: 6px; }\n.x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;\n color: #a1a1aa; padding: 2px 6px; cursor: pointer; }\n.done { font-size: 13px; color: #18181b; margin: 0; }\ninput { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; padding: 8px 10px;\n border: 1px solid #e4e4e7; border-radius: 8px; margin-bottom: 8px; }\n`;\n\nconst THANKS_MS = 1100; // brief acknowledgement, then auto-close\n\nexport function showSurvey(config: SurveyConfig): Promise<SurveyResult> {\n return new Promise((resolve) => {\n if (typeof document === \"undefined\") {\n resolve({ dismissed: true });\n return;\n }\n\n let settled = false;\n const host = document.createElement(\"div\");\n const shadow = host.attachShadow({ mode: \"open\" });\n\n function teardown() {\n try {\n host.remove();\n } catch {\n /* swallow */\n }\n }\n\n // Resolve immediately (so the answer is sent without delay) but leave a short\n // \"Thanks\" on screen before removing the node.\n function finish(result: SurveyResult, thank: boolean) {\n if (settled) return;\n settled = true;\n resolve(result);\n if (thank) {\n thankYou();\n setTimeout(teardown, THANKS_MS);\n } else {\n teardown();\n }\n }\n\n const style = document.createElement(\"style\");\n style.textContent = STYLE;\n shadow.appendChild(style);\n\n const wrap = document.createElement(\"div\");\n wrap.className = \"wrap\";\n shadow.appendChild(wrap);\n\n function card(): HTMLElement {\n wrap.innerHTML = \"\";\n const c = document.createElement(\"div\");\n c.className = \"card\";\n wrap.appendChild(c);\n return c;\n }\n\n function withClose(c: HTMLElement) {\n const x = document.createElement(\"button\");\n x.className = \"x\";\n x.textContent = \"×\";\n x.setAttribute(\"aria-label\", \"Dismiss\");\n x.onclick = () => finish({ dismissed: true }, false);\n c.appendChild(x);\n }\n\n function question() {\n const c = card();\n withClose(c);\n const q = document.createElement(\"p\");\n q.className = \"q\";\n q.textContent = config.question;\n c.appendChild(q);\n\n for (const opt of (config.options ?? []).slice(0, 6)) {\n const b = document.createElement(\"button\");\n b.className = \"opt\";\n b.textContent = opt;\n b.onclick = () => finish({ choice: opt }, true);\n c.appendChild(b);\n }\n\n if (config.allow_text) {\n const se = document.createElement(\"button\");\n se.className = \"opt\";\n // If there were no options, this is the only path — label it plainly.\n se.textContent = (config.options?.length ?? 0) > 0 ? \"Something else…\" : \"Answer…\";\n se.onclick = freeText;\n c.appendChild(se);\n }\n }\n\n function freeText() {\n const c = card();\n withClose(c);\n const q = document.createElement(\"p\");\n q.className = \"q\";\n q.textContent = config.question;\n c.appendChild(q);\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.placeholder = \"In your own words…\";\n c.appendChild(input);\n const row = document.createElement(\"div\");\n row.className = \"row\";\n const send = document.createElement(\"button\");\n send.className = \"primary\";\n send.textContent = \"Send\";\n const submit = () => {\n const text = input.value.trim();\n if (!text) {\n finish({ dismissed: true }, false);\n return;\n }\n finish({ text }, true);\n };\n send.onclick = submit;\n input.onkeydown = (e) => {\n if ((e as KeyboardEvent).key === \"Enter\") submit();\n };\n row.appendChild(send);\n c.appendChild(row);\n try {\n input.focus();\n } catch {\n /* swallow */\n }\n }\n\n function thankYou() {\n const c = card();\n const p = document.createElement(\"p\");\n p.className = \"done\";\n p.textContent = \"Thanks — that helps.\";\n c.appendChild(p);\n }\n\n try {\n document.body.appendChild(host);\n question();\n } catch {\n finish({ dismissed: true }, false);\n }\n });\n}\n"]}
|