@userquery/oneoff-discovery
v0.1.8
Published
Minimalist React (web) widget that invites users to a short, paid AI voice discovery interview. For Next.js / React 19.
Maintainers
Readme
@userquery/oneoff-discovery
A minimalist React (web) widget that invites users to an AI voice interview — triggered after a moment that matters (e.g. a purchase). Built for Next.js / React 19. Soft-black monochrome, draggable in-call bubble, records both sides of the call to the backend.
This is the web port of the React Native SDK. Everything runs in the browser (WebRTC / MediaRecorder), so the package is a Client Component (
"use client").
Install
npm install @userquery/oneoff-discoveryPeer deps: react and react-dom (>=18; works with React 19).
Quick start (Next.js App Router)
'use client';
import { useState } from 'react';
import { InterviewInvite } from '@userquery/oneoff-discovery';
export default function Checkout() {
const [purchased, setPurchased] = useState(false);
return (
<>
<button onClick={() => setPurchased(true)}>Complete purchase</button>
<InterviewInvite
apiBase="https://backend-provided.com" // Hidden Node API
trigger={purchased} // flip true after the event
title="Want $10 Off?"
body={"Take a quick **10-minute** interview and get **$10 off** this order."}
offer={{ amount: 10, durationMinutes: 10 }}
position="bottom-right"
onRespond={(r) => console.log('user', r)}
/>
</>
);
}In the Next.js App Router, render it from a Client Component (a file with
'use client'), since it uses browser-only APIs.
Mic + iframes: render it directly as a component. If you must embed it in a cross-origin
<iframe>, the parent must setallow="microphone"on the iframe tag — an iframe restricts permissions, it doesn't grant them, so the mic is blocked even after the user allows it. The type-to-answer fallback still works without a mic.
Props (highlights)
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| apiBase | string | — | Hidden backend base URL. |
| trigger | boolean \| number | — | true shows it; a number auto-shows after N ms. |
| title / body / ctaLabel | string | — | Copy. body supports **bold** and \n. |
| userId | string | 'anonymous' | Stored with the recording. |
| offer | { amount, currency, durationMinutes } | $10 / 10min | Reward; gates the "leave early" warning. |
| position | 'bottom-right' \| … | 'bottom-right' | Corner. The in-call bubble is draggable. |
| scheme / theme | 'light' \| 'dark' / overrides | 'light' | Monochrome tokens. |
| campaignKey / maxShows / snoozeAfterDismissMs | — | — | Frequency cap (persists in localStorage). |
| onRespond / onCallEnd | callbacks | — | Lifecycle hooks. |
Agent-native integration
Built to be dropped in by an AI coding agent in a single pass. Full agent guide: AGENTS.md — it's bundled in the package, so after install an agent can read node_modules/@userquery/oneoff-discovery/AGENTS.md (no web scraping needed).
TL;DR: npm install → render <InterviewInvite> inside a Client Component → set apiBase (secrets stay server-side) → wire trigger. To test, fire it when the user lands on checkout; in production, gate on engagement (recency, activity, repeat buyer…) so only engaged users are invited, paired with maxShows / snoozeAfterDismissMs.
License
MIT
