@usero/sdk 1.1.10 → 1.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/session-replay.cjs +71 -3
- package/dist/plugins/session-replay.cjs.map +1 -1
- package/dist/plugins/session-replay.d.cts +8 -0
- package/dist/plugins/session-replay.d.ts +8 -0
- package/dist/plugins/session-replay.js +71 -3
- package/dist/plugins/session-replay.js.map +1 -1
- package/dist/plugins/user-test.cjs +536 -247
- package/dist/plugins/user-test.cjs.map +1 -1
- package/dist/plugins/user-test.d.cts +50 -2
- package/dist/plugins/user-test.d.ts +50 -2
- package/dist/plugins/user-test.js +536 -247
- package/dist/plugins/user-test.js.map +1 -1
- package/dist/react.cjs +11 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -0
- package/dist/react.d.ts +1 -0
- package/dist/react.js +11 -1
- package/dist/react.js.map +1 -1
- package/dist/usero.iife.js +20 -20
- package/dist/usero.iife.js.map +1 -1
- package/dist/vanilla.cjs +26 -1
- package/dist/vanilla.cjs.map +1 -1
- package/dist/vanilla.d.cts +41 -1
- package/dist/vanilla.d.ts +41 -1
- package/dist/vanilla.js +26 -2
- package/dist/vanilla.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types.ts","../../src/plugins/user-test.ts"],"names":[],"mappings":";;;AA0IO,IAAM,eAAA,GAAkB,kBAAA;;;ACV/B,IAAM,eAAA,GAGF;AAAA,EACH,UAAA,EAAY,YAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMZ,YAAA,EAAc,EAAA;AAAA,EACd,MAAA,EAAQ,eAAA;AAAA,EACR,UAAA,EAAY,EAAA;AAAA,EACZ,aAAA,EAAe;AAChB,CAAA;AAEA,IAAM,uBAAA,GAA0B,6BAAA;AAChC,IAAM,4BAAA,GAA+B,kCAAA;AACrC,IAAM,QAAA,GAAW,iBAAA;AACjB,IAAM,SAAA,GAAY,gBAAA;AAWlB,SAAS,eAAe,QAAA,EAAsC;AAC7D,EAAA,IAAI,UAAU,OAAO,QAAA;AACrB,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,YAAA,EAAc,OAAA,CAAQ,uBAAuB,CAAA;AACnE,IAAA,IAAI,MAAA,IAAU,MAAA,CAAO,IAAA,EAAK,EAAG,OAAO,OAAO,IAAA,EAAK,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AAAA,EAC/D,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,MAAA;AACR;AAOA,SAAS,iBAAA,GAAmC;AAC3C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,QAAA,KAAa,aAAa,OAAO,IAAA;AACpF,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAA,CAAO,SAAS,MAAM,CAAA;AACzD,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA;AAC5B,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,MAAM,UAAU,GAAA,CAAI,IAAA,EAAK,CAAE,KAAA,CAAM,GAAG,EAAE,CAAA;AAEtC,IAAA,IAAI,CAAC,cAAA,CAAe,IAAA,CAAK,OAAO,GAAG,OAAO,IAAA;AAC1C,IAAA,OAAO,OAAA;AAAA,EACR,CAAA,CAAA,MAAQ;AACP,IAAA,OAAO,IAAA;AAAA,EACR;AACD;AAEA,SAAS,YAAY,UAAA,EAAmC;AACvD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,QAAA,KAAa,aAAa,OAAO,IAAA;AACpF,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAA,CAAO,SAAS,MAAM,CAAA;AACzD,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,GAAA,CAAI,UAAU,CAAA;AAClC,IAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,IAAA,MAAM,UAAU,IAAA,CAAK,IAAA,EAAK,CAAE,KAAA,CAAM,GAAG,EAAE,CAAA;AACvC,IAAA,IAAI,CAAC,eAAA,CAAgB,IAAA,CAAK,OAAO,GAAG,OAAO,IAAA;AAC3C,IAAA,OAAO,OAAA;AAAA,EACR,CAAA,CAAA,MAAQ;AACP,IAAA,OAAO,IAAA;AAAA,EACR;AACD;AAEA,SAAS,wBAAA,GAAoC;AAC5C,EAAA,OAAO,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,MAAA,CAAO,aAAA,KAAkB,WAAA,IAAe,OAAO,SAAA,KAAc,WAAA,IAAe,CAAC,CAAC,UAAU,YAAA,EAAc,YAAA;AACtJ;AAEA,SAAS,YAAA,GAAmC;AAC3C,EAAA,MAAM,UAAA,GAAa,CAAC,wBAAA,EAA0B,YAAA,EAAc,yBAAyB,WAAW,CAAA;AAChG,EAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AACnC,IAAA,IAAI,OAAO,aAAA,KAAkB,WAAA,IAAe,aAAA,CAAc,eAAA,GAAkB,SAAS,CAAA,EAAG;AACvF,MAAA,OAAO,SAAA;AAAA,IACR;AAAA,EACD;AACA,EAAA,OAAO,MAAA;AACR;AAKA,SAAS,OAAA,GAAuC;AAC/C,EAAA,OAAO,IAAI,QAAQ,CAAA,OAAA,KAAW;AAC7B,IAAA,IAAI,OAAO,cAAc,WAAA,EAAa;AACrC,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA;AAAA,IACD;AACA,IAAA,IAAI;AACH,MAAA,MAAM,GAAA,GAAM,SAAA,CAAU,IAAA,CAAK,QAAA,EAAU,CAAC,CAAA;AACtC,MAAA,GAAA,CAAI,kBAAkB,MAAY;AACjC,QAAA,MAAM,KAAK,GAAA,CAAI,MAAA;AACf,QAAA,IAAI,CAAC,EAAA,CAAG,gBAAA,CAAiB,QAAA,CAAS,SAAS,CAAA,EAAG;AAC7C,UAAA,EAAA,CAAG,iBAAA,CAAkB,SAAA,EAAW,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,QAClD;AAAA,MACD,CAAA;AACA,MAAA,GAAA,CAAI,SAAA,GAAY,MAAY,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AAC9C,MAAA,GAAA,CAAI,OAAA,GAAU,MAAY,OAAA,CAAQ,IAAI,CAAA;AAAA,IACvC,CAAA,CAAA,MAAQ;AACP,MAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,IACb;AAAA,EACD,CAAC,CAAA;AACF;AAEA,eAAe,cAAc,KAAA,EAAoC;AAChE,EAAA,MAAM,EAAA,GAAK,MAAM,OAAA,EAAQ;AACzB,EAAA,IAAI,CAAC,EAAA,EAAI;AACT,EAAA,MAAM,IAAI,QAAc,CAAA,OAAA,KAAW;AAClC,IAAA,IAAI;AACH,MAAA,MAAM,EAAA,GAAK,EAAA,CAAG,WAAA,CAAY,SAAA,EAAW,WAAW,CAAA;AAChD,MAAA,EAAA,CAAG,WAAA,CAAY,SAAS,CAAA,CAAE,GAAA,CAAI,KAAK,CAAA;AACnC,MAAA,EAAA,CAAG,UAAA,GAAa,MAAY,OAAA,EAAQ;AACpC,MAAA,EAAA,CAAG,OAAA,GAAU,MAAY,OAAA,EAAQ;AACjC,MAAA,EAAA,CAAG,OAAA,GAAU,MAAY,OAAA,EAAQ;AAAA,IAClC,CAAA,CAAA,MAAQ;AACP,MAAA,OAAA,EAAQ;AAAA,IACT;AAAA,EACD,CAAC,CAAA;AACD,EAAA,EAAA,CAAG,KAAA,EAAM;AACV;AAEA,eAAe,eAAe,EAAA,EAA2B;AACxD,EAAA,MAAM,EAAA,GAAK,MAAM,OAAA,EAAQ;AACzB,EAAA,IAAI,CAAC,EAAA,EAAI;AACT,EAAA,MAAM,IAAI,QAAc,CAAA,OAAA,KAAW;AAClC,IAAA,IAAI;AACH,MAAA,MAAM,EAAA,GAAK,EAAA,CAAG,WAAA,CAAY,SAAA,EAAW,WAAW,CAAA;AAChD,MAAA,EAAA,CAAG,WAAA,CAAY,SAAS,CAAA,CAAE,MAAA,CAAO,EAAE,CAAA;AACnC,MAAA,EAAA,CAAG,UAAA,GAAa,MAAY,OAAA,EAAQ;AACpC,MAAA,EAAA,CAAG,OAAA,GAAU,MAAY,OAAA,EAAQ;AACjC,MAAA,EAAA,CAAG,OAAA,GAAU,MAAY,OAAA,EAAQ;AAAA,IAClC,CAAA,CAAA,MAAQ;AACP,MAAA,OAAA,EAAQ;AAAA,IACT;AAAA,EACD,CAAC,CAAA;AACD,EAAA,EAAA,CAAG,KAAA,EAAM;AACV;AAEA,eAAe,cAAc,SAAA,EAA4C;AACxE,EAAA,MAAM,EAAA,GAAK,MAAM,OAAA,EAAQ;AACzB,EAAA,IAAI,CAAC,EAAA,EAAI,OAAO,EAAC;AACjB,EAAA,MAAM,KAAA,GAAQ,MAAM,IAAI,OAAA,CAAwB,CAAA,OAAA,KAAW;AAC1D,IAAA,IAAI;AACH,MAAA,MAAM,EAAA,GAAK,EAAA,CAAG,WAAA,CAAY,SAAA,EAAW,UAAU,CAAA;AAC/C,MAAA,MAAM,GAAA,GAAM,EAAA,CAAG,WAAA,CAAY,SAAS,EAAE,MAAA,EAAO;AAC7C,MAAA,GAAA,CAAI,YAAY,MAAY;AAC3B,QAAA,MAAM,GAAA,GAAO,GAAA,CAAI,MAAA,IAA6B,EAAC;AAC/C,QAAA,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,SAAA,KAAc,SAAS,CAAC,CAAA;AAAA,MACnD,CAAA;AACA,MAAA,GAAA,CAAI,OAAA,GAAU,MAAY,OAAA,CAAQ,EAAE,CAAA;AAAA,IACrC,CAAA,CAAA,MAAQ;AACP,MAAA,OAAA,CAAQ,EAAE,CAAA;AAAA,IACX;AAAA,EACD,CAAC,CAAA;AACD,EAAA,EAAA,CAAG,KAAA,EAAM;AACT,EAAA,OAAO,KAAA;AACR;AAEA,eAAe,qBACd,MAAA,EACA,SAAA,EACA,OACA,IAAA,EACA,MAAA,EACA,cAAc,CAAA,EACK;AACnB,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,wBAAA,EAA2B,kBAAA,CAAmB,SAAS,CAAC,CAAA,aAAA,EAAgB,KAAK,CAAA,CAAA;AACrH,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,OAAO,UAAU,WAAA,EAAa;AAC7B,IAAA,IAAI;AACH,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAC5B,MAAA,EAAQ,KAAA;AAAA,QACR,IAAA,EAAM,IAAA;AAAA,QACN,OAAA,EAAS,EAAE,cAAA,EAAgB,IAAA,CAAK,QAAQ,YAAA,EAAa;AAAA,QACrD,SAAA,EAAW,IAAA,CAAK,IAAA,IAAQ,EAAA,GAAK;AAAA;AAAA,OAC7B,CAAA;AACD,MAAA,IAAI,GAAA,CAAI,IAAI,OAAO,IAAA;AAEnB,MAAA,IAAI,GAAA,CAAI,MAAA,IAAU,GAAA,IAAO,GAAA,CAAI,MAAA,GAAS,GAAA,IAAO,GAAA,CAAI,MAAA,KAAW,GAAA,IAAO,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK;AACtF,QAAA,MAAA,CAAO,MAAM,CAAA,MAAA,EAAS,KAAK,CAAA,eAAA,EAAkB,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACzD,QAAA,OAAO,KAAA;AAAA,MACR;AAAA,IACD,SAAS,GAAA,EAAK;AACb,MAAA,MAAA,CAAO,KAAK,CAAA,MAAA,EAAS,KAAK,mBAAmB,OAAA,GAAU,CAAC,WAAW,GAAG,CAAA;AAAA,IACvE;AACA,IAAA,OAAA,IAAW,CAAA;AACX,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,IAAA,EAAO,GAAA,GAAM,CAAA,IAAK,OAAO,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,KAAW,GAAG,CAAA;AACpF,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,KAAA;AACR;AASA,SAAS,cAAA,CAAe,IAAA,EAAmB,KAAA,EAAsB,SAAA,EAA2C;AAC3G,EAAA,MAAM,OAAO,IAAA,CAAK,YAAA,CAAa,EAAE,IAAA,EAAM,UAAU,CAAA;AACjD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAG5C,EAAA,KAAA,CAAM,WAAA,GAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","file":"user-test.cjs","sourcesContent":["// Shared types used by both the vanilla and React entry points.\n// Keep this file framework-free so the vanilla bundle never pulls react.\n\nexport type FeedbackRating = 1 | 2 | 3 | 4\n\nexport interface FeedbackMetadata {\n\tpageUrl: string\n\tpageTitle: string\n\treferrer?: string\n\ttimestamp: number\n}\n\nexport interface ScreenshotData {\n\tfileName: string\n\turl: string\n\tfileSize: number\n\twidth?: number\n\theight?: number\n\tmimeType: string\n}\n\nexport interface FeedbackSubmission {\n\tclientId: string\n\trating?: FeedbackRating\n\tcomment?: string\n\tuserEmail?: string\n\tpageUrl: string\n\tpageTitle: string\n\treferrer?: string\n\tenvironment?: string\n\tscreenshots?: ScreenshotData[]\n\tmetadata?: Record<string, unknown>\n\t// Legacy gzipped + base64-encoded rrweb event stream. The pre-chunked\n\t// session-replay plugin attached this on submit. The chunked-upload\n\t// plugin (>= 0.4.0) does NOT set this — it ships events out-of-band\n\t// via the chunk endpoints and points at the resulting session replay\n\t// via `sessionReplayId` + `replayOffsetMs`. Kept on the wire one\n\t// release for backward compat with old SaaS deployments that only\n\t// know how to ingest the legacy field.\n\treplayEvents?: string\n\t// Pointer to a SessionReplay row created by the session-replay\n\t// plugin's chunked-upload pipeline, plus the offset within that\n\t// recording at submit time so the dashboard can deep-link.\n\tsessionReplayId?: string\n\treplayOffsetMs?: number\n}\n\nexport interface FeedbackData {\n\trating?: FeedbackRating\n\tcomment?: string\n\tuserEmail?: string\n\tscreenshots?: ScreenshotData[]\n\tmetadata: FeedbackMetadata\n}\n\n// Customer-supplied identity for the session-replay / identify pipeline.\n// Use the declarative form: pass `user` on the React widget (the SDK\n// diffs and auto-fires identify), or supply `getUser` on the vanilla\n// init (the SDK polls it at session start). An imperative `identify()`\n// call exists as an escape hatch only and is intentionally not\n// documented as the headline API.\nexport type UseroUserTraitValue = string | number | boolean | null\nexport type UseroUserTraits = Record<string, UseroUserTraitValue>\nexport interface UseroUser {\n\tid: string\n\temail?: string\n\tdisplayName?: string\n\ttraits?: UseroUserTraits\n}\n\nexport type WidgetPosition = 'right' | 'left'\n\nexport interface WidgetTheme {\n\tprimary: string\n\tbackground: string\n\ttext: string\n\tborder: string\n\tshadow: string\n}\n\n// Forward-declared to avoid a circular import. The plugin module imports\n// FeedbackSubmission from this file, so we can't import UseroPlugin back\n// up here without a cycle. Keeping the prop typed as an opaque object with\n// the minimal shape the widget actually inspects works fine for consumers.\nexport interface FeedbackWidgetProps {\n\tclientId: string\n\tposition?: WidgetPosition\n\ttheme?: Partial<WidgetTheme>\n\ttitle?: string\n\tplaceholder?: string\n\tshowEmailOption?: boolean\n\tshowScreenshotOption?: boolean\n\tenvironment?: string\n\tbaseUrl?: string\n\tmetadata?: Record<string, unknown>\n\tplugins?: ReadonlyArray<import('./plugin').UseroPlugin>\n\t// Declarative identity. React: pass the current user (or null on\n\t// logout) and the SDK auto-fires identify when the resolved id\n\t// transitions. Vanilla: pass a getter so the SDK can resolve user at\n\t// session start / chunk boundaries. Pass at most one. The SDK never\n\t// invokes both.\n\tuser?: UseroUser | null\n\tgetUser?: () => UseroUser | null | undefined\n\tonSubmit?: (feedback: FeedbackData) => void\n\tonError?: (error: Error) => void\n\tonOpen?: () => void\n\tonClose?: () => void\n}\n\nexport interface SubmissionResponse {\n\tsuccess: boolean\n\terror?: string\n\tid?: string\n\tmessage?: string\n\tdata?: unknown\n}\n\nexport const EMOJI_MAP: Record<FeedbackRating, string> = {\n\t1: '😞',\n\t2: '😐',\n\t3: '😊',\n\t4: '🤩',\n}\n\nexport const RATING_LABELS: Record<FeedbackRating, string> = {\n\t1: 'Needs work',\n\t2: \"It's okay\",\n\t3: 'Pretty good',\n\t4: 'Amazing!',\n}\n\nexport const EMOJI_BACKGROUNDS: Record<FeedbackRating, string> = {\n\t1: 'linear-gradient(135deg,#ff6b6b14,#ff6b6b1f)',\n\t2: 'linear-gradient(135deg,#9ca3af0f,#9ca3af1a)',\n\t3: 'linear-gradient(135deg,#3b82f614,#3b82f61f)',\n\t4: 'linear-gradient(135deg,#f59e0b14,#f59e0b1f)',\n}\n\nexport const DEFAULT_API_URL = 'https://usero.io'\n\nexport const DEFAULT_THEME: WidgetTheme = {\n\tprimary: '#2563eb',\n\tbackground: '#ffffff',\n\ttext: '#374151',\n\tborder: '#e5e7eb',\n\tshadow:\n\t\t'0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',\n}\n\nexport const DARK_THEME: WidgetTheme = {\n\tprimary: '#2563eb',\n\tbackground: '#1f2937',\n\ttext: '#f9fafb',\n\tborder: '#374151',\n\tshadow:\n\t\t'0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2)',\n}\n\nexport function mergeTheme(customTheme: Partial<WidgetTheme> = {}): WidgetTheme {\n\treturn { ...DEFAULT_THEME, ...customTheme }\n}\n","// User-testing-mode plugin for the Usero widget.\n//\n// Activates ONLY when the host page URL carries `?usero_test=<slug>`. When\n// active, it:\n// 1. Creates a UserTestSession on the SaaS side via\n// POST /api/user-test-sessions\n// 2. Starts a MediaRecorder on the user's microphone (Opus in WebM) and\n// ships chunks every `chunkSeconds` to\n// PUT /api/user-test-sessions/:id/chunk?index=N\n// 3. Renders a small floating \"recording\" indicator with a Finish button\n// bottom-center so the tester can stop on their own.\n// 4. On Finish (or `pagehide` / `visibilitychange` -> hidden), flushes any\n// buffered chunks and calls POST /api/user-test-sessions/:id/finalise.\n//\n// Mic-permission denied is non-fatal: the session is still created (the\n// session-replay plugin keeps recording in parallel) and the indicator\n// shows a \"no audio\" hint. The SaaS side detects audio presence from the\n// first chunk; if no chunks land, hasAudio stays false there too.\n//\n// Bundle hygiene: this module is a subpath export and does NOT depend on\n// rrweb or any other heavy dep — just the native MediaRecorder API. The\n// floating UI is shadow-DOM scoped so host page CSS can't leak in.\n//\n// Privacy: only the microphone is recorded. We never read DOM text, never\n// touch the camera, and never persist audio in the browser past the\n// IndexedDB fallback (cleared on successful upload).\n\nimport type { UseroPlugin, PluginContext } from '../plugin'\nimport { DEFAULT_API_URL } from '../types'\n\nexport interface UserTestOptions {\n\t// URL query param the welcome page appends to redirect testers back.\n\t// Default `usero_test`. Match SaaS side if you ever change it.\n\tqueryParam?: string\n\t// Audio chunk length in seconds. Smaller = more partial-data resilience\n\t// but more requests. Default 10.\n\tchunkSeconds?: number\n\t// API origin. Override for self-hosted or local dev. Defaults to the\n\t// shared SDK constant (https://usero.io).\n\tapiUrl?: string\n\t// Override the tester-name shown on the SaaS side. Normally the welcome\n\t// page collects this and stores it in localStorage; the plugin reads it\n\t// from there. This option lets a host site bypass that.\n\ttesterName?: string\n\t// Hide the floating indicator. The plugin still records and finalises\n\t// on `pagehide`, but the tester gets no on-page UI. Useful if the host\n\t// page wants to render its own.\n\thideIndicator?: boolean\n}\n\ninterface UserTestTask {\n\tid: string\n\tprompt: string\n\tsortOrder: number\n}\n\ninterface MutedSegment {\n\tstartMs: number\n\tendMs: number\n}\n\ninterface InFlightNote {\n\tatMs: number\n\ttext: string\n\tacked: boolean\n\tserverId?: string\n}\n\ninterface RecorderStore {\n\tcancelled: boolean\n\tslug: string\n\tsessionId: string | null\n\tclientId: string | null\n\trecorder: MediaRecorder | null\n\tstream: MediaStream | null\n\tchunkIndex: number\n\tuploadQueue: Promise<void>\n\tpendingUploads: number\n\tstartedAt: number\n\tindicator: HTMLElement | null\n\tindicatorRoot: ShadowRoot | null\n\tindicatorState: 'recording' | 'finishing' | 'done' | 'no-audio' | 'error'\n\tpageHideHandler: (() => void) | null\n\tvisibilityHandler: (() => void) | null\n\toptions: Required<UserTestOptions>\n\ttasks: UserTestTask[]\n\ttasksPanelOpen: boolean\n\toutsidePointerHandler: ((event: PointerEvent) => void) | null\n\tkeydownHandler: ((event: KeyboardEvent) => void) | null\n\t// Mic mute\n\thasMicPermission: boolean\n\t// True while getUserMedia is in flight (pending). Distinguishes the\n\t// \"connecting\" chip state (granted users, promise not yet resolved) from\n\t// the genuinely-failed terminal \"none\" state. Init true; cleared on every\n\t// success/failure exit of startRecording.\n\tmicAcquiring: boolean\n\t// Why mic acquisition failed, for actionable terminal copy. 'blocked' =\n\t// permission denied (NotAllowedError), 'not-found' = no device\n\t// (NotFoundError), 'unsupported' = MediaRecorder/getUserMedia missing or\n\t// constructor threw. Null while acquiring or once granted.\n\tmicFailReason: 'blocked' | 'not-found' | 'unsupported' | null\n\tmuted: boolean\n\tmutedSinceMs: number | null\n\tmutedSegments: MutedSegment[]\n\t// Silent-mic guard. micSilent is true while the live input is reading\n\t// digital silence (dead mic, or a virtual audio device delivering nothing).\n\t// Non-blocking: recording continues; the pill just warns. Auto-clears when\n\t// real audio returns. silenceMonitor holds the AnalyserNode teardown.\n\tmicSilent: boolean\n\tsilenceMonitor: { stop(): void } | null\n\tmuteToastShown: boolean\n\tmuteToastTimers: number[]\n\t// In-flight notes\n\tnotes: InFlightNote[]\n\tnotesPopoverOpen: boolean\n\tnotePopoverAtMs: number | null\n\t// End-of-test comment (collected on thanks screen)\n\tendNote: string\n\t// Re-entry guard for finishFlow.\n\tfinishFlowRan: boolean\n\t// Offset (ms) into the session-replay recording at the moment THIS\n\t// user-test session started. Captured once at start (not at finalise)\n\t// so it pins the test timeline to the recording timeline. Null when no\n\t// replay is active (plugin not loaded, sampled out, or host predates\n\t// the core accessor); finalise then omits replayOffsetMs.\n\treplayOffsetAtStartMs: number | null\n}\n\nconst DEFAULT_OPTIONS: Required<Omit<UserTestOptions, 'testerName' | 'apiUrl'>> & {\n\ttesterName: string\n\tapiUrl: string\n} = {\n\tqueryParam: 'usero_test',\n\t// 10s (not 30) so at most ~10s of audio is at risk if the tab is torn\n\t// down before a flush, and so a session shorter than the old 30s window\n\t// still emits at least one chunk (previously its single buffered chunk was\n\t// never flushed and its audio was lost). Tradeoff: ~3x the R2 writes /\n\t// upload requests per session; 10s is an acceptable balance, don't go lower.\n\tchunkSeconds: 10,\n\tapiUrl: DEFAULT_API_URL,\n\ttesterName: '',\n\thideIndicator: false,\n}\n\nconst TESTER_NAME_STORAGE_KEY = 'usero:user-test:tester-name'\nconst TASKS_PANEL_OPEN_STORAGE_KEY = 'usero:user-test:tasks-panel-open'\nconst IDB_NAME = 'usero-user-test'\nconst IDB_STORE = 'pending-chunks'\n\ninterface PendingChunk {\n\tid: string\n\tsessionId: string\n\tapiUrl: string\n\tchunkIndex: number\n\tblob: Blob\n\tcreatedAt: number\n}\n\nfunction readTesterName(override: string): string | undefined {\n\tif (override) return override\n\ttry {\n\t\tconst stored = window.localStorage?.getItem(TESTER_NAME_STORAGE_KEY)\n\t\tif (stored && stored.trim()) return stored.trim().slice(0, 120)\n\t} catch {\n\t\t// Storage access can throw in some sandboxed iframes — ignore.\n\t}\n\treturn undefined\n}\n\n// Read the `uts` (user-test session) id the entry screen appends when it\n// creates the session server-side. When present, the SDK ADOPTS that session\n// instead of minting its own (so the session has the participant's email from\n// creation, no double-session). Absent for open tests using the old link\n// shape, where the SDK falls back to createSession.\nfunction getAdoptSessionId(): string | null {\n\tif (typeof window === 'undefined' || typeof window.location === 'undefined') return null\n\ttry {\n\t\tconst params = new URLSearchParams(window.location.search)\n\t\tconst raw = params.get('uts')\n\t\tif (!raw) return null\n\t\tconst cleaned = raw.trim().slice(0, 64)\n\t\t// Session ids are cuids: lowercase alphanumerics. Reject anything else.\n\t\tif (!/^[a-z0-9]+$/i.test(cleaned)) return null\n\t\treturn cleaned\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction getTestSlug(queryParam: string): string | null {\n\tif (typeof window === 'undefined' || typeof window.location === 'undefined') return null\n\ttry {\n\t\tconst params = new URLSearchParams(window.location.search)\n\t\tconst slug = params.get(queryParam)\n\t\tif (!slug) return null\n\t\tconst cleaned = slug.trim().slice(0, 64)\n\t\tif (!/^[a-z0-9-]+$/i.test(cleaned)) return null\n\t\treturn cleaned\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction isMediaRecorderSupported(): boolean {\n\treturn typeof window !== 'undefined' && typeof window.MediaRecorder !== 'undefined' && typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getUserMedia\n}\n\nfunction pickMimeType(): string | undefined {\n\tconst candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus', 'audio/mp4']\n\tfor (const candidate of candidates) {\n\t\tif (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported?.(candidate)) {\n\t\t\treturn candidate\n\t\t}\n\t}\n\treturn undefined\n}\n\n// IndexedDB helpers. Best-effort, never throw upstream — if IDB is missing\n// or quota-blown we just lose offline resilience but the live upload path\n// still runs.\nfunction idbOpen(): Promise<IDBDatabase | null> {\n\treturn new Promise(resolve => {\n\t\tif (typeof indexedDB === 'undefined') {\n\t\t\tresolve(null)\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tconst req = indexedDB.open(IDB_NAME, 1)\n\t\t\treq.onupgradeneeded = (): void => {\n\t\t\t\tconst db = req.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE, { keyPath: 'id' })\n\t\t\t\t}\n\t\t\t}\n\t\t\treq.onsuccess = (): void => resolve(req.result)\n\t\t\treq.onerror = (): void => resolve(null)\n\t\t} catch {\n\t\t\tresolve(null)\n\t\t}\n\t})\n}\n\nasync function idbStashChunk(chunk: PendingChunk): Promise<void> {\n\tconst db = await idbOpen()\n\tif (!db) return\n\tawait new Promise<void>(resolve => {\n\t\ttry {\n\t\t\tconst tx = db.transaction(IDB_STORE, 'readwrite')\n\t\t\ttx.objectStore(IDB_STORE).put(chunk)\n\t\t\ttx.oncomplete = (): void => resolve()\n\t\t\ttx.onerror = (): void => resolve()\n\t\t\ttx.onabort = (): void => resolve()\n\t\t} catch {\n\t\t\tresolve()\n\t\t}\n\t})\n\tdb.close()\n}\n\nasync function idbDeleteChunk(id: string): Promise<void> {\n\tconst db = await idbOpen()\n\tif (!db) return\n\tawait new Promise<void>(resolve => {\n\t\ttry {\n\t\t\tconst tx = db.transaction(IDB_STORE, 'readwrite')\n\t\t\ttx.objectStore(IDB_STORE).delete(id)\n\t\t\ttx.oncomplete = (): void => resolve()\n\t\t\ttx.onerror = (): void => resolve()\n\t\t\ttx.onabort = (): void => resolve()\n\t\t} catch {\n\t\t\tresolve()\n\t\t}\n\t})\n\tdb.close()\n}\n\nasync function idbListChunks(sessionId: string): Promise<PendingChunk[]> {\n\tconst db = await idbOpen()\n\tif (!db) return []\n\tconst items = await new Promise<PendingChunk[]>(resolve => {\n\t\ttry {\n\t\t\tconst tx = db.transaction(IDB_STORE, 'readonly')\n\t\t\tconst req = tx.objectStore(IDB_STORE).getAll()\n\t\t\treq.onsuccess = (): void => {\n\t\t\t\tconst all = (req.result as PendingChunk[]) ?? []\n\t\t\t\tresolve(all.filter(c => c.sessionId === sessionId))\n\t\t\t}\n\t\t\treq.onerror = (): void => resolve([])\n\t\t} catch {\n\t\t\tresolve([])\n\t\t}\n\t})\n\tdb.close()\n\treturn items\n}\n\nasync function uploadChunkWithRetry(\n\tapiUrl: string,\n\tsessionId: string,\n\tindex: number,\n\tblob: Blob,\n\tlogger: PluginContext['logger'],\n\tmaxAttempts = 5,\n): Promise<boolean> {\n\tconst url = `${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/chunk?index=${index}`\n\tlet attempt = 0\n\twhile (attempt < maxAttempts) {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbody: blob,\n\t\t\t\theaders: { 'Content-Type': blob.type || 'audio/webm' },\n\t\t\t\tkeepalive: blob.size <= 60 * 1024, // browsers cap keepalive bodies\n\t\t\t})\n\t\t\tif (res.ok) return true\n\t\t\t// 4xx (other than 413) won't get better with retries; bail early.\n\t\t\tif (res.status >= 400 && res.status < 500 && res.status !== 408 && res.status !== 429) {\n\t\t\t\tlogger.error(`chunk ${index} rejected with ${res.status}`)\n\t\t\t\treturn false\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlogger.warn(`chunk ${index} upload attempt ${attempt + 1} failed`, err)\n\t\t}\n\t\tattempt += 1\n\t\tconst backoff = Math.min(15000, 500 * 2 ** attempt) + Math.floor(Math.random() * 250)\n\t\tawait new Promise(resolve => setTimeout(resolve, backoff))\n\t}\n\treturn false\n}\n\ninterface IndicatorCallbacks {\n\tonFinish: () => void\n\tonToggleTasks: () => void\n\tonToggleMute: () => void\n\tonOpenNote: () => void\n}\n\nfunction buildIndicator(host: HTMLElement, store: RecorderStore, callbacks: IndicatorCallbacks): ShadowRoot {\n\tconst root = host.attachShadow({ mode: 'closed' })\n\tconst style = document.createElement('style')\n\t// Compact, glassy dark pill. Mic chip is now a real button with three\n\t// states (recording / muted / no-mic). Notes button sits beside it.\n\tstyle.textContent = `\n\t\t:host { all: initial; }\n\t\t.anchor {\n\t\t\tposition: fixed;\n\t\t\tbottom: calc(env(safe-area-inset-bottom, 0px) + 16px);\n\t\t\tleft: 50%; transform: translateX(-50%);\n\t\t\tdisplay: flex; flex-direction: column; align-items: center; gap: 8px;\n\t\t\tz-index: 2147483646; max-width: calc(100vw - 32px);\n\t\t\tfont: 13px/1 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n\t\t\tcolor: #fff;\n\t\t}\n\t\t.bar {\n\t\t\tdisplay: inline-flex; align-items: center; gap: 6px;\n\t\t\tpadding: 6px 8px 6px 6px;\n\t\t\tbackground: rgba(17,17,17,0.82);\n\t\t\tborder: 1px solid rgba(255,255,255,0.08);\n\t\t\tborder-radius: 999px;\n\t\t\tbox-shadow: 0 8px 24px rgba(0,0,0,0.22);\n\t\t\tbackdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);\n\t\t\tmax-width: 100%;\n\t\t}\n\t\t.panel {\n\t\t\tbackground: rgba(17,17,17,0.92);\n\t\t\tborder: 1px solid rgba(255,255,255,0.08);\n\t\t\tborder-radius: 14px; padding: 12px 14px 12px 8px;\n\t\t\tline-height: 1.45;\n\t\t\tbox-shadow: 0 12px 32px rgba(0,0,0,0.32);\n\t\t\tmax-height: min(60vh, 480px);\n\t\t\tmax-width: min(420px, calc(100vw - 32px));\n\t\t\twidth: max-content; overflow-y: auto;\n\t\t}\n\t\t.panel[hidden] { display: none; }\n\t\t.panel ol { margin: 0; padding-left: 26px; }\n\t\t.panel li { margin: 0 0 8px; }\n\t\t.panel li:last-child { margin: 0; }\n\n\t\t/* Mic chip: pill-within-pill with dot + label, doubles as mute toggle. */\n\t\t.mic {\n\t\t\tdisplay: inline-flex; align-items: center; gap: 7px;\n\t\t\tmin-height: 32px; min-width: 44px;\n\t\t\tpadding: 0 11px 0 10px;\n\t\t\tborder-radius: 999px;\n\t\t\tbackground: rgba(255,255,255,0.06);\n\t\t\tborder: 1px solid rgba(255,255,255,0.06);\n\t\t\tcolor: #fff; font: inherit;\n\t\t\tcursor: pointer; appearance: none;\n\t\t\ttransition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\t\t}\n\t\t.mic:hover { background: rgba(255,255,255,0.12); }\n\t\t.mic:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\t\t.mic[data-mic-state=\"muted\"] {\n\t\t\tbackground: rgba(251, 191, 36, 0.18);\n\t\t\tborder-color: rgba(251, 191, 36, 0.45);\n\t\t\tcolor: #fcd34d;\n\t\t}\n\t\t.mic[data-mic-state=\"muted\"]:hover { background: rgba(251, 191, 36, 0.26); }\n\t\t/* Connecting: getUserMedia pending. Steady amber tint reads as \"working\",\n\t\t distinct from the live red pulse and the failed state below. The icon\n\t\t gets a gentle non-pulsing breathe so it feels alive without alarming. */\n\t\t.mic[data-mic-state=\"connecting\"] {\n\t\t\tbackground: rgba(251, 191, 36, 0.14);\n\t\t\tborder-color: rgba(251, 191, 36, 0.32);\n\t\t\tcolor: #fcd34d;\n\t\t\tcursor: default;\n\t\t}\n\t\t.mic[data-mic-state=\"connecting\"]:hover { background: rgba(251, 191, 36, 0.14); }\n\t\t.mic[data-mic-state=\"connecting\"] .mic-icon {\n\t\t\tcolor: #fbbf24;\n\t\t\tanimation: micBreathe 1.4s ease-in-out infinite;\n\t\t}\n\t\t/* Failed terminal state, actionable. Tappable affordance: clearer border,\n\t\t pointer cursor, brightens on hover/focus to invite the retry tap. */\n\t\t.mic[data-mic-state=\"none\"] {\n\t\t\tbackground: rgba(255,255,255,0.05);\n\t\t\tborder-color: rgba(255,255,255,0.14);\n\t\t\tcolor: rgba(255,255,255,0.72);\n\t\t\tcursor: pointer;\n\t\t}\n\t\t.mic[data-mic-state=\"none\"]:hover {\n\t\t\tbackground: rgba(255,255,255,0.12);\n\t\t\tborder-color: rgba(255,255,255,0.24);\n\t\t\tcolor: #fff;\n\t\t}\n\t\t@keyframes micBreathe {\n\t\t\t0%, 100% { opacity: 0.55; }\n\t\t\t50% { opacity: 1; }\n\t\t}\n\t\t.mic-icon { width: 13px; height: 13px; display: inline-block; flex-shrink: 0; }\n\t\t.mic-label { font-weight: 500; letter-spacing: 0.01em; white-space: nowrap; }\n\n\t\t.dot {\n\t\t\twidth: 7px; height: 7px; border-radius: 50%;\n\t\t\tbackground: #ef4444;\n\t\t\tbox-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6);\n\t\t\tanimation: pulse 1.6s ease-out infinite;\n\t\t\tflex-shrink: 0;\n\t\t}\n\t\t.dot[data-state=\"no-audio\"] { background: #fbbf24; animation: none; }\n\t\t.dot[data-state=\"finishing\"] { background: #fbbf24; animation: none; }\n\t\t.dot[data-state=\"done\"] { background: #10b981; animation: none; }\n\t\t.dot[data-state=\"error\"] { background: #ef4444; animation: none; }\n\n\t\t.btn {\n\t\t\tappearance: none; border: 0; background: rgba(255,255,255,0.10);\n\t\t\tcolor: #fff; font: inherit; font-weight: 600;\n\t\t\tpadding: 6px 12px; min-height: 32px; border-radius: 999px; cursor: pointer;\n\t\t\ttransition: background 0.15s ease, transform 0.06s ease;\n\t\t\tdisplay: inline-flex; align-items: center; gap: 6px;\n\t\t}\n\t\t.btn:hover { background: rgba(255,255,255,0.20); }\n\t\t.btn:active { transform: scale(0.97); }\n\t\t.btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\t\t.btn[disabled] { opacity: 0.5; cursor: progress; }\n\t\t.tasks-btn[aria-expanded=\"true\"] { background: rgba(255,255,255,0.24); }\n\n\t\t/* Note button: icon-only, matches mic chip footprint */\n\t\t.note-btn {\n\t\t\twidth: 32px; min-height: 32px; padding: 0;\n\t\t\tbackground: rgba(255,255,255,0.06);\n\t\t\tborder: 1px solid rgba(255,255,255,0.06);\n\t\t\tborder-radius: 999px;\n\t\t\tdisplay: inline-flex; align-items: center; justify-content: center; gap: 4px;\n\t\t\tcolor: #fff; font: inherit; cursor: pointer; appearance: none;\n\t\t\ttransition: background 0.15s ease, border-color 0.15s ease, width 0.18s ease;\n\t\t\toverflow: hidden;\n\t\t}\n\t\t.note-btn:hover { background: rgba(255,255,255,0.14); }\n\t\t.note-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\t\t.note-btn[data-has-notes=\"true\"] { width: auto; padding: 0 10px 0 9px; gap: 6px; }\n\t\t.note-btn[aria-expanded=\"true\"] { background: rgba(255,255,255,0.22); border-color: rgba(255,255,255,0.18); }\n\t\t.note-icon { width: 14px; height: 14px; display: inline-block; }\n\t\t.note-count { font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }\n\n\t\t.spacer { width: 1px; height: 18px; background: rgba(255,255,255,0.14); margin: 0 1px; }\n\n\t\t@media (max-width: 480px) {\n\t\t\t.bar { gap: 4px; padding: 5px 6px 5px 5px; }\n\t\t\t.btn { padding: 7px 12px; min-height: 38px; }\n\t\t\t.mic, .note-btn { min-height: 38px; }\n\t\t\t.note-btn { width: 38px; }\n\t\t\t.note-btn[data-has-notes=\"true\"] { width: auto; }\n\t\t}\n\n\t\t/* First-mute helper toast: sits above the pill, auto-dismisses */\n\t\t.toast {\n\t\t\tbackground: rgba(17,17,17,0.92);\n\t\t\tborder: 1px solid rgba(251, 191, 36, 0.45);\n\t\t\tcolor: #fff;\n\t\t\tpadding: 9px 14px; border-radius: 12px;\n\t\t\tmax-width: min(340px, calc(100vw - 32px));\n\t\t\tbox-shadow: 0 12px 28px rgba(0,0,0,0.28);\n\t\t\ttext-align: center; line-height: 1.4;\n\t\t\tanimation: toast-in 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);\n\t\t}\n\t\t.toast[data-leaving=\"true\"] { animation: toast-out 0.24s ease forwards; }\n\t\t.toast strong { color: #fcd34d; font-weight: 600; }\n\t\t@keyframes toast-in {\n\t\t\tfrom { opacity: 0; transform: translateY(6px); }\n\t\t\tto { opacity: 1; transform: translateY(0); }\n\t\t}\n\t\t@keyframes toast-out {\n\t\t\tto { opacity: 0; transform: translateY(4px); }\n\t\t}\n\n\t\t/* Notes popover */\n\t\t.note-popover {\n\t\t\tbackground: rgba(17,17,17,0.94);\n\t\t\tborder: 1px solid rgba(255,255,255,0.10);\n\t\t\tborder-radius: 14px; padding: 12px;\n\t\t\twidth: min(340px, calc(100vw - 32px));\n\t\t\tbox-shadow: 0 18px 40px rgba(0,0,0,0.36);\n\t\t\tdisplay: flex; flex-direction: column; gap: 10px;\n\t\t\tanimation: pop-in 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);\n\t\t}\n\t\t.note-popover[hidden] { display: none; }\n\t\t@keyframes pop-in {\n\t\t\tfrom { opacity: 0; transform: translateY(6px) scale(0.98); }\n\t\t\tto { opacity: 1; transform: translateY(0) scale(1); }\n\t\t}\n\t\t.note-head {\n\t\t\tcolor: rgba(255,255,255,0.7); font-size: 12px;\n\t\t\tfont-weight: 500; letter-spacing: 0.02em;\n\t\t}\n\t\t.note-textarea {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tmin-height: 80px; resize: vertical;\n\t\t\tpadding: 10px 11px;\n\t\t\tbackground: rgba(0,0,0,0.35);\n\t\t\tborder: 1px solid rgba(255,255,255,0.10);\n\t\t\tborder-radius: 10px;\n\t\t\tcolor: #fff; font: inherit; font-size: 13.5px;\n\t\t\tline-height: 1.45;\n\t\t\ttransition: border-color 0.15s ease;\n\t\t}\n\t\t.note-textarea:focus { outline: none; border-color: rgba(255,255,255,0.32); }\n\t\t.note-textarea::placeholder { color: rgba(255,255,255,0.42); }\n\t\t.note-actions {\n\t\t\tdisplay: flex; align-items: center; justify-content: space-between; gap: 8px;\n\t\t}\n\t\t.note-actions .hint {\n\t\t\tcolor: rgba(255,255,255,0.45); font-size: 11px;\n\t\t}\n\t\t.note-actions .group { display: inline-flex; gap: 6px; }\n\t\t.note-actions .btn { padding: 6px 12px; font-size: 12.5px; min-height: 32px; }\n\t\t.btn-primary { background: #fff !important; color: #111; }\n\t\t.btn-primary:hover { background: rgba(255,255,255,0.85) !important; }\n\t\t.btn-ghost { background: transparent; color: rgba(255,255,255,0.7); }\n\t\t.btn-ghost:hover { background: rgba(255,255,255,0.10); color: #fff; }\n\n\t\t/* ---- Finished screen (complete + ended-early). Usero warm-stone palette,\n\t\t shadow-DOM scoped so host CSS can't leak in. Scrollable so the primary\n\t\t action stays reachable on a short phone with the keyboard open. ---- */\n\t\t.thanks {\n\t\t\tposition: fixed; inset: 0;\n\t\t\tdisplay: flex; align-items: flex-start; justify-content: center;\n\t\t\tbackground: rgba(28, 25, 23, 0.62);\n\t\t\tbackdrop-filter: blur(8px);\n\t\t\t-webkit-backdrop-filter: blur(8px);\n\t\t\tcolor: #1c1917;\n\t\t\tfont-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n\t\t\tz-index: 2147483647;\n\t\t\tpadding: 24px 16px calc(env(safe-area-inset-bottom, 0px) + 24px);\n\t\t\toverflow-y: auto;\n\t\t\t-webkit-overflow-scrolling: touch;\n\t\t}\n\t\t.thanks-card {\n\t\t\tbackground: #fff; color: #1c1917;\n\t\t\tborder-radius: 22px; padding: 30px 24px 24px;\n\t\t\tmax-width: 400px; width: 100%;\n\t\t\tmargin: auto 0;\n\t\t\tbox-shadow: 0 24px 60px rgba(28, 25, 23, 0.28), 0 2px 8px rgba(28, 25, 23, 0.12);\n\t\t\ttext-align: left;\n\t\t\tanimation: thanks-in 0.34s cubic-bezier(0.16, 1, 0.3, 1);\n\t\t}\n\t\t@keyframes thanks-in {\n\t\t\tfrom { opacity: 0; transform: translateY(14px) scale(0.985); }\n\t\t\tto { opacity: 1; transform: translateY(0) scale(1); }\n\t\t}\n\t\t.thanks-card .head { text-align: center; }\n\t\t.thanks h2 {\n\t\t\tmargin: 0 0 7px; font-size: 22px; line-height: 1.2;\n\t\t\tfont-weight: 600; letter-spacing: -0.018em; color: #1c1917;\n\t\t}\n\t\t.thanks .lede {\n\t\t\tmargin: 0 auto 22px; font-size: 14.5px; line-height: 1.5;\n\t\t\tcolor: #57534e; text-align: center; max-width: 30ch;\n\t\t}\n\n\t\t/* Status medallion: green tick when complete, warm ring when ended early */\n\t\t.thanks .check {\n\t\t\twidth: 56px; height: 56px; border-radius: 50%;\n\t\t\tdisplay: grid; place-items: center;\n\t\t\tmargin: 0 auto 16px;\n\t\t}\n\t\t.thanks .check.ok {\n\t\t\tbackground: #ecfdf5;\n\t\t\tbox-shadow: inset 0 0 0 1px rgba(16,185,129,0.22);\n\t\t\tcolor: #059669;\n\t\t}\n\t\t.thanks .check.ok svg { width: 26px; height: 26px; }\n\t\t.thanks .check.early {\n\t\t\tbackground: #fff7ed;\n\t\t\tbox-shadow: inset 0 0 0 1px rgba(234,88,12,0.20);\n\t\t\tcolor: #ea580c;\n\t\t}\n\t\t.thanks .check.early svg { width: 24px; height: 24px; }\n\n\t\t/* Verified-checks list (complete) / progress list (ended early) */\n\t\t.thanks .checks {\n\t\t\tlist-style: none; margin: 0 0 4px; padding: 0;\n\t\t\tborder: 1px solid #f0eeec; border-radius: 14px;\n\t\t\tbackground: #fafaf9; overflow: hidden;\n\t\t}\n\t\t.thanks .checks li {\n\t\t\tdisplay: flex; align-items: center; gap: 11px;\n\t\t\tpadding: 12px 14px; font-size: 14px; color: #292524;\n\t\t\tborder-top: 1px solid #f0eeec;\n\t\t}\n\t\t.thanks .checks li:first-child { border-top: 0; }\n\t\t.thanks .checks .ic {\n\t\t\twidth: 20px; height: 20px; border-radius: 50%;\n\t\t\tdisplay: grid; place-items: center; flex-shrink: 0;\n\t\t}\n\t\t.thanks .checks .ic.done { background: #d1fae5; color: #059669; }\n\t\t.thanks .checks .ic.todo { background: #f5f5f4; color: #a8a29e; box-shadow: inset 0 0 0 1px #e7e5e4; }\n\t\t.thanks .checks .ic svg { width: 12px; height: 12px; }\n\t\t.thanks .checks li.muted-row { color: #78716c; }\n\n\t\t/* Payout block (complete) */\n\t\t.thanks .payout { margin-top: 20px; }\n\t\t.thanks .payout-q {\n\t\t\tfont-size: 12px; font-weight: 600; letter-spacing: 0.04em;\n\t\t\ttext-transform: uppercase; color: #a8a29e;\n\t\t\tmargin: 0 0 10px;\n\t\t}\n\t\t.thanks .pay-primary {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tappearance: none; border: 0; cursor: pointer;\n\t\t\tbackground: #ea580c; color: #fff;\n\t\t\tpadding: 15px 18px; border-radius: 14px;\n\t\t\tfont: inherit; font-weight: 600; font-size: 15.5px;\n\t\t\tline-height: 1.3; text-align: center;\n\t\t\tbox-shadow: 0 6px 16px rgba(234, 88, 12, 0.28);\n\t\t\ttransition: background 0.15s ease, transform 0.07s ease, box-shadow 0.15s ease;\n\t\t}\n\t\t.thanks .pay-primary:hover { background: #c2410c; }\n\t\t.thanks .pay-primary:active { transform: scale(0.985); }\n\t\t.thanks .pay-primary:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; }\n\t\t.thanks .pay-primary[disabled] { opacity: 0.6; cursor: progress; box-shadow: none; }\n\t\t.thanks .pay-primary .amt { font-variant-numeric: tabular-nums; }\n\t\t.thanks .pay-alt {\n\t\t\tdisplay: block; width: 100%;\n\t\t\tmargin-top: 12px; padding: 4px;\n\t\t\tbackground: none; border: 0; cursor: pointer;\n\t\t\tfont: inherit; font-size: 13px; font-weight: 500;\n\t\t\tcolor: #78716c; text-align: center;\n\t\t\ttext-decoration: underline; text-underline-offset: 2px;\n\t\t\ttransition: color 0.15s ease;\n\t\t}\n\t\t.thanks .pay-alt:hover { color: #44403c; }\n\t\t.thanks .pay-alt:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; border-radius: 6px; }\n\t\t.thanks [hidden] { display: none !important; }\n\n\t\t/* Alternate-email expander */\n\t\t.thanks .pay-edit { margin-top: 14px; animation: pop-in 0.2s cubic-bezier(0.2,0.8,0.2,1); }\n\t\t.thanks .pay-edit[hidden] { display: none; }\n\t\t.thanks .pay-label {\n\t\t\tdisplay: block; margin: 0 0 7px;\n\t\t\tfont-size: 13px; font-weight: 500; color: #44403c;\n\t\t}\n\t\t.thanks .pay-input {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tpadding: 12px 13px;\n\t\t\tbackground: #fff; border: 1px solid #e7e5e4; border-radius: 11px;\n\t\t\tfont: inherit; font-size: 15px; color: #1c1917;\n\t\t\ttransition: border-color 0.15s ease, box-shadow 0.15s ease;\n\t\t}\n\t\t.thanks .pay-input:focus {\n\t\t\toutline: none; border-color: #ea580c;\n\t\t\tbox-shadow: 0 0 0 3px rgba(234, 88, 12, 0.16);\n\t\t}\n\t\t.thanks .pay-input::placeholder { color: #a8a29e; }\n\t\t.thanks .pay-eta {\n\t\t\tmargin: 14px 0 0; font-size: 12px; line-height: 1.45;\n\t\t\tcolor: #a8a29e; text-align: center;\n\t\t}\n\n\t\t/* Ended-early \"what unlocks the reward\" note */\n\t\t.thanks .early-note {\n\t\t\tdisplay: flex; align-items: flex-start; gap: 10px;\n\t\t\tmargin-top: 18px; padding: 13px 14px;\n\t\t\tbackground: #fff7ed; border: 1px solid #fed7aa; border-radius: 13px;\n\t\t\tfont-size: 13.5px; line-height: 1.45; color: #9a3412;\n\t\t}\n\t\t.thanks .early-note svg { width: 17px; height: 17px; flex-shrink: 0; margin-top: 1px; color: #ea580c; }\n\t\t.thanks .early-actions { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }\n\t\t.thanks .resume-btn {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tappearance: none; border: 0; cursor: pointer;\n\t\t\tbackground: #ea580c; color: #fff;\n\t\t\tpadding: 15px 18px; border-radius: 14px;\n\t\t\tfont: inherit; font-weight: 600; font-size: 15.5px;\n\t\t\tbox-shadow: 0 6px 16px rgba(234, 88, 12, 0.28);\n\t\t\ttransition: background 0.15s ease, transform 0.07s ease;\n\t\t}\n\t\t.thanks .resume-btn:hover { background: #c2410c; }\n\t\t.thanks .resume-btn:active { transform: scale(0.985); }\n\t\t.thanks .resume-btn:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; }\n\t\t.thanks .exit-btn {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tappearance: none; border: 0; background: none; cursor: pointer;\n\t\t\tpadding: 4px; font: inherit; font-size: 13px; line-height: 1.45;\n\t\t\tcolor: #78716c; text-align: center;\n\t\t}\n\t\t.thanks .exit-btn:hover { color: #44403c; }\n\t\t.thanks .exit-btn:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; border-radius: 6px; }\n\n\t\t/* End-of-test note (shown after payout is set, complete path only) */\n\t\t.thanks .note-section {\n\t\t\tmargin-top: 22px; padding-top: 20px;\n\t\t\tborder-top: 1px solid #f0eeec;\n\t\t}\n\t\t.thanks .end-label {\n\t\t\tdisplay: block; margin: 0 0 8px;\n\t\t\tfont-size: 13px; font-weight: 500; color: #44403c;\n\t\t}\n\t\t.thanks .end-textarea {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tmin-height: 84px; resize: vertical;\n\t\t\tpadding: 12px 13px;\n\t\t\tbackground: #fafaf9;\n\t\t\tborder: 1px solid #e7e5e4;\n\t\t\tborder-radius: 12px;\n\t\t\tfont: inherit; font-size: 14.5px; line-height: 1.5;\n\t\t\tcolor: #1c1917;\n\t\t\ttransition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;\n\t\t}\n\t\t.thanks .end-textarea:focus {\n\t\t\toutline: none; border-color: #ea580c; background: #fff;\n\t\t\tbox-shadow: 0 0 0 3px rgba(234, 88, 12, 0.14);\n\t\t}\n\t\t.thanks .end-textarea::placeholder { color: #a8a29e; }\n\t\t.thanks .end-actions {\n\t\t\tdisplay: flex; gap: 10px; margin-top: 14px;\n\t\t}\n\t\t.thanks .end-actions button {\n\t\t\tflex: 1;\n\t\t\tappearance: none; border: 1px solid #e7e5e4;\n\t\t\tbackground: #fff; color: #44403c;\n\t\t\tpadding: 12px 14px; border-radius: 12px;\n\t\t\tfont: inherit; font-weight: 600; font-size: 14px;\n\t\t\tcursor: pointer;\n\t\t\ttransition: background 0.15s ease, border-color 0.15s ease;\n\t\t}\n\t\t.thanks .end-actions button:hover { background: #fafaf9; border-color: #d6d3d1; }\n\t\t.thanks .end-actions button.primary {\n\t\t\tbackground: #1c1917; color: #fff; border-color: #1c1917; flex: 1.4;\n\t\t}\n\t\t.thanks .end-actions button.primary:hover { background: #292524; border-color: #292524; }\n\t\t.thanks .end-actions button:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; }\n\t\t.thanks .end-hint {\n\t\t\tmargin: 11px 0 0; font-size: 11.5px; color: #a8a29e; text-align: center;\n\t\t}\n\t\t.thanks .end-sent {\n\t\t\tmargin-top: 16px; text-align: center; color: #57534e; font-size: 13.5px; line-height: 1.45;\n\t\t}\n\t\t@media (prefers-reduced-motion: reduce) {\n\t\t\t.thanks-card, .thanks .pay-edit { animation: none; }\n\t\t}\n\n\t\t@keyframes pulse {\n\t\t\t0% { box-shadow: 0 0 0 0 rgba(239,68,68,0.55); }\n\t\t\t70% { box-shadow: 0 0 0 10px rgba(239,68,68,0); }\n\t\t\t100% { box-shadow: 0 0 0 0 rgba(239,68,68,0); }\n\t\t}\n\t\t@media (prefers-reduced-motion: reduce) {\n\t\t\t.dot { animation: none; }\n\t\t\t.toast, .note-popover { animation: none; }\n\t\t}\n\t`\n\tconst anchor = document.createElement('div')\n\tanchor.className = 'anchor'\n\n\tconst panel = document.createElement('div')\n\tpanel.className = 'panel'\n\tpanel.hidden = true\n\n\t// Toast slot: helper messages render here above the bar.\n\tconst toastSlot = document.createElement('div')\n\ttoastSlot.className = 'toast-slot'\n\n\t// Notes popover slot: rendered above the bar when open.\n\tconst notePopover = document.createElement('div')\n\tnotePopover.className = 'note-popover'\n\tnotePopover.hidden = true\n\n\tconst bar = document.createElement('div')\n\tbar.className = 'bar'\n\tbar.setAttribute('role', 'status')\n\tbar.setAttribute('aria-live', 'polite')\n\n\t// Mic chip = real button. Three states driven by data-mic-state.\n\tconst micBtn = document.createElement('button')\n\tmicBtn.type = 'button'\n\tmicBtn.className = 'mic'\n\tmicBtn.setAttribute('data-mic-state', 'recording')\n\tmicBtn.setAttribute('aria-pressed', 'false')\n\tmicBtn.setAttribute('aria-label', 'Mute microphone')\n\n\tconst dot = document.createElement('span')\n\tdot.className = 'dot'\n\tdot.setAttribute('data-state', store.indicatorState)\n\n\tconst micIcon = document.createElement('span')\n\tmicIcon.className = 'mic-icon'\n\tmicIcon.innerHTML = MIC_ICON_SVG\n\tmicIcon.setAttribute('aria-hidden', 'true')\n\n\tconst micLabel = document.createElement('span')\n\tmicLabel.className = 'mic-label'\n\tmicLabel.textContent = 'Recording'\n\n\tmicBtn.appendChild(dot)\n\tmicBtn.appendChild(micIcon)\n\tmicBtn.appendChild(micLabel)\n\tmicBtn.addEventListener('click', callbacks.onToggleMute)\n\tbar.appendChild(micBtn)\n\n\t// Notes button: icon-only by default, grows to show count once notes exist.\n\tconst noteBtn = document.createElement('button')\n\tnoteBtn.type = 'button'\n\tnoteBtn.className = 'note-btn'\n\tnoteBtn.setAttribute('aria-label', 'Add a timestamped note')\n\tnoteBtn.setAttribute('aria-expanded', 'false')\n\tnoteBtn.setAttribute('data-has-notes', 'false')\n\tnoteBtn.innerHTML = `<span class=\"note-icon\" aria-hidden=\"true\">${NOTE_ICON_SVG}</span><span class=\"note-count\" hidden></span>`\n\tnoteBtn.addEventListener('click', callbacks.onOpenNote)\n\tbar.appendChild(noteBtn)\n\n\tconst spacer = document.createElement('span')\n\tspacer.className = 'spacer'\n\tbar.appendChild(spacer)\n\n\tconst btn = document.createElement('button')\n\tbtn.type = 'button'\n\tbtn.className = 'btn finish-btn'\n\tbtn.textContent = 'Finish'\n\tbtn.addEventListener('click', callbacks.onFinish)\n\tbar.appendChild(btn)\n\n\tif (store.tasks.length > 0) installTasksToggle(bar, btn, store, callbacks.onToggleTasks)\n\n\tanchor.appendChild(panel)\n\tanchor.appendChild(toastSlot)\n\tanchor.appendChild(notePopover)\n\tanchor.appendChild(bar)\n\n\troot.appendChild(style)\n\troot.appendChild(anchor)\n\treturn root\n}\n\n// Inline SVGs kept tiny. currentColor so they inherit the chip text color.\nconst MIC_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\"><path d=\"M8 1.5a2 2 0 0 0-2 2v4a2 2 0 1 0 4 0v-4a2 2 0 0 0-2-2Z\" fill=\"currentColor\"/><path d=\"M4 7.5a4 4 0 0 0 8 0M8 11.5v3M5.5 14.5h5\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>`\nconst MIC_MUTED_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\"><path d=\"M8 1.5a2 2 0 0 0-2 2v3.2L10 11V3.5a2 2 0 0 0-2-2Z\" fill=\"currentColor\"/><path d=\"M4 7.5a4 4 0 0 0 6.5 3.12M12 7.5a4 4 0 0 1-.3 1.5M8 11.5v3M5.5 14.5h5M2 2l12 12\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>`\nconst NOTE_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\"><path d=\"M3 3.5A1.5 1.5 0 0 1 4.5 2h7A1.5 1.5 0 0 1 13 3.5V10a1.5 1.5 0 0 1-1.5 1.5H7L4 14v-2.5h-.5A1.5 1.5 0 0 1 2 10V3.5A1.5 1.5 0 0 1 3.5 3\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\n// Thanks-screen icons: bold tick (medallion + done rows) and a clock (ended early).\nconst TICK_ICON_SVG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M5 12.5 10 17.5 19 7\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\nconst TICK_SM_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M3.5 8.5 6.5 11.5 12.5 5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\nconst CLOCK_ICON_SVG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><circle cx=\"12\" cy=\"12\" r=\"8.4\" stroke=\"currentColor\" stroke-width=\"2\"/><path d=\"M12 7.5V12l3 2\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\nconst SPARK_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8 1.5 9.5 6.5 14.5 8 9.5 9.5 8 14.5 6.5 9.5 1.5 8 6.5 6.5Z\" fill=\"currentColor\"/></svg>`\n\nfunction installTasksToggle(bar: HTMLElement, finishBtn: HTMLElement, store: RecorderStore, onToggleTasks: () => void): void {\n\tconst tasksBtn = document.createElement('button')\n\ttasksBtn.type = 'button'\n\ttasksBtn.className = 'btn tasks-btn'\n\ttasksBtn.textContent = `Tasks (${store.tasks.length})`\n\ttasksBtn.setAttribute('aria-expanded', store.tasksPanelOpen ? 'true' : 'false')\n\ttasksBtn.addEventListener('click', onToggleTasks)\n\tbar.insertBefore(tasksBtn, finishBtn)\n}\n\nfunction renderTasksPanel(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst panel = root.querySelector('.panel')\n\tif (!(panel instanceof HTMLElement)) return\n\t// Build content once.\n\tif (!panel.firstChild && store.tasks.length > 0) {\n\t\tconst ol = document.createElement('ol')\n\t\tfor (const task of store.tasks) {\n\t\t\tconst li = document.createElement('li')\n\t\t\tli.textContent = task.prompt\n\t\t\tol.appendChild(li)\n\t\t}\n\t\tpanel.appendChild(ol)\n\t}\n\tpanel.hidden = !store.tasksPanelOpen\n\tconst tasksBtn = root.querySelector('.tasks-btn')\n\tif (tasksBtn instanceof HTMLElement) {\n\t\ttasksBtn.setAttribute('aria-expanded', store.tasksPanelOpen ? 'true' : 'false')\n\t}\n}\n\nfunction readTasksPanelOpen(): boolean {\n\ttry { return window.sessionStorage?.getItem(TASKS_PANEL_OPEN_STORAGE_KEY) === '1' } catch { return false }\n}\nfunction writeTasksPanelOpen(open: boolean): void {\n\ttry { window.sessionStorage?.setItem(TASKS_PANEL_OPEN_STORAGE_KEY, open ? '1' : '0') } catch { /* ignore */ }\n}\n\nfunction micChipState(store: RecorderStore): 'recording' | 'muted' | 'none' | 'connecting' | 'silent' | 'inactive' {\n\tif (store.indicatorState === 'finishing' || store.indicatorState === 'done' || store.indicatorState === 'error') {\n\t\treturn 'inactive'\n\t}\n\tif (!store.hasMicPermission) {\n\t\t// Pending getUserMedia: show \"connecting\" so granted users never flash\n\t\t// the failure copy. Once startRecording resolves or rejects it clears\n\t\t// micAcquiring, and we fall through to the terminal \"none\" state.\n\t\tif (store.micAcquiring) return 'connecting'\n\t\treturn 'none'\n\t}\n\tif (store.muted) return 'muted'\n\t// Permission granted and not muted, but the live track is reading digital\n\t// silence (dead mic or a virtual silent input device). Warn, non-blocking:\n\t// recording continues, this just prompts the participant to check their mic.\n\tif (store.micSilent) return 'silent'\n\treturn 'recording'\n}\n\nfunction renderIndicatorState(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst dot = root.querySelector('.dot')\n\tconst mic = root.querySelector<HTMLButtonElement>('.mic')\n\tconst micIcon = root.querySelector('.mic-icon')\n\tconst micLabel = root.querySelector('.mic-label')\n\tconst btn = root.querySelector<HTMLButtonElement>('.finish-btn')\n\tif (!(dot instanceof HTMLElement) || !mic || !(micIcon instanceof HTMLElement) || !(micLabel instanceof HTMLElement) || !btn) return\n\n\tdot.setAttribute('data-state', store.indicatorState)\n\tconst chipState = micChipState(store)\n\t// The silent-mic warning reuses the existing \"none\" warning treatment\n\t// (muted-grey, tappable retry affordance) rather than inventing a new visual\n\t// — same as the \"Mic blocked, tap to retry\" failed state, just different copy.\n\tconst micStateAttr = chipState === 'inactive' || chipState === 'silent' ? 'none' : chipState\n\tmic.setAttribute('data-mic-state', micStateAttr)\n\t// Distinguish \"acquiring\" (genuinely failed, actionable) from \"connecting\"\n\t// at the attribute level so the dot/visuals key off the right state. The\n\t// failed terminal chip is a retry affordance; mark it so CSS can style it.\n\tmic.removeAttribute('data-mic-fail')\n\tif (chipState === 'none') mic.setAttribute('data-mic-fail', store.micFailReason ?? 'blocked')\n\n\t// Finish-button copy is driven by the indicatorState (network / lifecycle).\n\tswitch (store.indicatorState) {\n\t\tcase 'recording':\n\t\tcase 'no-audio':\n\t\t\tbtn.textContent = 'Finish'\n\t\t\tbtn.disabled = false\n\t\t\tbreak\n\t\tcase 'finishing':\n\t\t\tbtn.textContent = 'Saving'\n\t\t\tbtn.disabled = true\n\t\t\tbreak\n\t\tcase 'done':\n\t\t\tbtn.textContent = 'Done'\n\t\t\tbtn.disabled = true\n\t\t\tbreak\n\t\tcase 'error':\n\t\t\tbtn.textContent = 'Retry'\n\t\t\tbtn.disabled = false\n\t\t\tbreak\n\t}\n\n\t// Mic chip copy + icon. Replay continues in all states; the chip only\n\t// describes the audio track.\n\tswitch (chipState) {\n\t\tcase 'recording':\n\t\t\tmicIcon.innerHTML = MIC_ICON_SVG\n\t\t\tmicLabel.textContent = 'Recording'\n\t\t\tmic.setAttribute('aria-label', 'Mute microphone')\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\tcase 'muted':\n\t\t\tmicIcon.innerHTML = MIC_MUTED_ICON_SVG\n\t\t\tmicLabel.textContent = 'Muted'\n\t\t\tmic.setAttribute('aria-label', 'Unmute microphone')\n\t\t\tmic.setAttribute('aria-pressed', 'true')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\tcase 'connecting':\n\t\t\t// getUserMedia still pending. Granted users sit here briefly instead\n\t\t\t// of flashing the failure copy. Not yet a toggle, so unfocusable.\n\t\t\tmicIcon.innerHTML = MIC_ICON_SVG\n\t\t\tmicLabel.textContent = 'Connecting mic'\n\t\t\tmic.setAttribute('aria-label', 'Connecting microphone')\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.setAttribute('tabindex', '-1')\n\t\t\tbreak\n\t\tcase 'silent':\n\t\t\t// Permission granted, recording live, but the input is digital\n\t\t\t// silence (dead mic or a virtual silent device). Warn, non-blocking:\n\t\t\t// recording continues. Tappable so the participant can re-acquire the\n\t\t\t// mic after switching their input device. Auto-clears when real audio\n\t\t\t// returns (the monitor flips store.micSilent back to false).\n\t\t\tmicIcon.innerHTML = MIC_MUTED_ICON_SVG\n\t\t\tmicLabel.textContent = \"We can't hear you, tap to recheck\"\n\t\t\tmic.setAttribute('aria-label', \"We can't hear your microphone. Check your input device, then tap to recheck. Recording continues.\")\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\tcase 'none': {\n\t\t\t// Genuinely failed terminal state. Actionable: the chip is a button\n\t\t\t// that re-attempts mic acquisition. Keyboard-focusable (no tabindex\n\t\t\t// -1). Replay keeps recording regardless.\n\t\t\tmicIcon.innerHTML = MIC_MUTED_ICON_SVG\n\t\t\tconst failLabel =\n\t\t\t\tstore.micFailReason === 'not-found' ? 'No mic found, tap to retry' :\n\t\t\t\t'Mic blocked, tap to retry'\n\t\t\tconst failAria =\n\t\t\t\tstore.micFailReason === 'not-found'\n\t\t\t\t\t? 'No microphone found, tap to retry. Replay continues.'\n\t\t\t\t\t: 'Microphone blocked, tap to retry. Replay continues.'\n\t\t\tmicLabel.textContent = failLabel\n\t\t\tmic.setAttribute('aria-label', failAria)\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\t}\n\t\tcase 'inactive':\n\t\t\tmicIcon.innerHTML = MIC_ICON_SVG\n\t\t\tmicLabel.textContent =\n\t\t\t\tstore.indicatorState === 'finishing' ? 'Saving' :\n\t\t\t\tstore.indicatorState === 'done' ? 'Saved' :\n\t\t\t\t'Save failed'\n\t\t\tmic.setAttribute('aria-label', 'Recording stopped')\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.setAttribute('tabindex', '-1')\n\t\t\tbreak\n\t}\n}\n\nfunction renderNotesCount(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst noteBtn = root.querySelector('.note-btn')\n\tconst count = root.querySelector('.note-count')\n\tif (!(noteBtn instanceof HTMLElement) || !(count instanceof HTMLElement)) return\n\tconst n = store.notes.length\n\tnoteBtn.setAttribute('data-has-notes', n > 0 ? 'true' : 'false')\n\tif (n > 0) {\n\t\tcount.textContent = String(n)\n\t\tcount.hidden = false\n\t\tnoteBtn.setAttribute('aria-label', `Add a timestamped note (${n} so far)`)\n\t} else {\n\t\tcount.textContent = ''\n\t\tcount.hidden = true\n\t\tnoteBtn.setAttribute('aria-label', 'Add a timestamped note')\n\t}\n}\n\nfunction showMuteToast(store: RecorderStore): void {\n\tif (store.muteToastShown) return\n\tstore.muteToastShown = true\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst slot = root.querySelector('.toast-slot')\n\tif (!(slot instanceof HTMLElement)) return\n\tslot.innerHTML = ''\n\tconst toast = document.createElement('div')\n\ttoast.className = 'toast'\n\ttoast.setAttribute('role', 'status')\n\ttoast.innerHTML = `<strong>Mic off.</strong> Screen is still recording. Tap to unmute.`\n\tslot.appendChild(toast)\n\tconst outer = window.setTimeout(() => {\n\t\tif (!toast.isConnected) return\n\t\ttoast.setAttribute('data-leaving', 'true')\n\t\tconst inner = window.setTimeout(() => {\n\t\t\tif (toast.isConnected) toast.remove()\n\t\t}, 260)\n\t\tstore.muteToastTimers.push(inner)\n\t}, 3000)\n\tstore.muteToastTimers.push(outer)\n}\n\nfunction openNotePopover(store: RecorderStore, onSave: (text: string) => void, onCancel: () => void): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst pop = root.querySelector('.note-popover')\n\tconst noteBtn = root.querySelector('.note-btn')\n\tif (!(pop instanceof HTMLElement) || !(noteBtn instanceof HTMLElement)) return\n\n\tstore.notesPopoverOpen = true\n\tstore.notePopoverAtMs = Date.now() - store.startedAt\n\tnoteBtn.setAttribute('aria-expanded', 'true')\n\n\tpop.innerHTML = ''\n\tconst head = document.createElement('div')\n\thead.className = 'note-head'\n\thead.innerHTML = `<span>Add a note</span>`\n\n\tconst form = document.createElement('form')\n\tform.style.cssText = 'display:flex;flex-direction:column;gap:10px;margin:0;'\n\tform.noValidate = true\n\n\tconst ta = document.createElement('textarea')\n\tta.className = 'note-textarea'\n\tta.placeholder = 'What just happened? Confusing? Surprising? Broken?'\n\tta.rows = 3\n\tta.setAttribute('aria-label', 'Note text')\n\n\tconst actions = document.createElement('div')\n\tactions.className = 'note-actions'\n\tconst hint = document.createElement('span')\n\thint.className = 'hint'\n\thint.innerHTML = '<kbd style=\"font-family:inherit\">Cmd</kbd>+Enter to save'\n\tconst group = document.createElement('div')\n\tgroup.className = 'group'\n\tconst cancelBtn = document.createElement('button')\n\tcancelBtn.type = 'button'\n\tcancelBtn.className = 'btn btn-ghost'\n\tcancelBtn.textContent = 'Cancel'\n\tconst saveBtn = document.createElement('button')\n\tsaveBtn.type = 'submit'\n\tsaveBtn.className = 'btn btn-primary'\n\tsaveBtn.textContent = 'Save'\n\tgroup.appendChild(cancelBtn)\n\tgroup.appendChild(saveBtn)\n\tactions.appendChild(hint)\n\tactions.appendChild(group)\n\n\tform.appendChild(ta)\n\tform.appendChild(actions)\n\n\tpop.appendChild(head)\n\tpop.appendChild(form)\n\tpop.hidden = false\n\n\tconst submit = (): void => {\n\t\tconst text = ta.value.trim()\n\t\tif (!text) { onCancel(); return }\n\t\tonSave(text)\n\t}\n\tform.addEventListener('submit', e => { e.preventDefault(); submit() })\n\tcancelBtn.addEventListener('click', () => onCancel())\n\tta.addEventListener('keydown', e => {\n\t\tif ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n\t\t\te.preventDefault()\n\t\t\tsubmit()\n\t\t} else if (e.key === 'Escape') {\n\t\t\te.preventDefault()\n\t\t\tonCancel()\n\t\t}\n\t})\n\n\t// Autofocus on next frame so animation can finish without scroll jank.\n\twindow.requestAnimationFrame(() => { ta.focus({ preventScroll: true }) })\n}\n\nfunction closeNotePopover(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst pop = root.querySelector('.note-popover')\n\tconst noteBtn = root.querySelector('.note-btn')\n\tif (pop instanceof HTMLElement) {\n\t\tpop.hidden = true\n\t\tpop.innerHTML = ''\n\t}\n\tif (noteBtn instanceof HTMLElement) noteBtn.setAttribute('aria-expanded', 'false')\n\tstore.notesPopoverOpen = false\n\tstore.notePopoverAtMs = null\n}\n\n// Escape user-controlled strings before they touch innerHTML. The payout email\n// comes from our own DB, but it originated as participant input, so treat it as\n// untrusted and never interpolate it raw into markup.\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, '&')\n\t\t.replace(/</g, '<')\n\t\t.replace(/>/g, '>')\n\t\t.replace(/\"/g, '"')\n\t\t.replace(/'/g, ''')\n}\n\nfunction isValidEmail(value: string): boolean {\n\t// Pragmatic check; the server re-validates with zod .email().\n\treturn /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)\n}\n\ninterface ThanksOptions {\n\t// Payment summary from the first finalise response. Null when the server is\n\t// older / didn't return it: we fall back to the neutral note-only screen.\n\tpayment: PaymentSummary | null\n\t// Confirm the participant's payout destination. Resolves true on success.\n\tonPayout: (destination: string | null) => Promise<boolean>\n\t// End-of-test wrap-up note (complete path). Throws on failure so the UI can retry.\n\tonSubmitNote: (text: string) => Promise<void> | void\n\tonSkip: () => void\n\t// Re-arm recording and dismiss the overlay (ended-early path \"Resume\").\n\tonResume: () => void\n}\n\nfunction showThanksScreen(root: ShadowRoot, opts: ThanksOptions): void {\n\tconst overlay = document.createElement('div')\n\toverlay.className = 'thanks'\n\toverlay.setAttribute('role', 'dialog')\n\toverlay.setAttribute('aria-modal', 'true')\n\n\tconst card = document.createElement('div')\n\tcard.className = 'thanks-card'\n\toverlay.appendChild(card)\n\troot.appendChild(overlay)\n\n\t// Ended-early branch: warmer, non-punishing, keep Resume primary.\n\tif (opts.payment && !opts.payment.qualified) {\n\t\trenderEndedEarly(card, opts)\n\t\treturn\n\t}\n\n\t// Complete branch (also the fallback when payment is null: a clean \"saved\"\n\t// confirmation with the wrap-up note, no payout block since we have no data).\n\trenderComplete(card, opts)\n}\n\n// Builds the verified-checks list. `done` rows get the green tick; an unfinished\n// tasks row (ended-early) gets the hollow todo dot.\nfunction checksList(rows: Array<{ label: string; done: boolean; muted?: boolean }>): string {\n\tconst items = rows\n\t\t.map(r => {\n\t\t\tconst icClass = r.done ? 'ic done' : 'ic todo'\n\t\t\tconst icon = r.done ? TICK_SM_SVG : ''\n\t\t\tconst liClass = r.muted ? ' class=\"muted-row\"' : ''\n\t\t\treturn `<li${liClass}><span class=\"${icClass}\" aria-hidden=\"true\">${icon}</span><span>${escapeHtml(r.label)}</span></li>`\n\t\t})\n\t\t.join('')\n\treturn `<ul class=\"checks\">${items}</ul>`\n}\n\nfunction renderComplete(card: HTMLElement, opts: ThanksOptions): void {\n\tconst payment = opts.payment\n\tconst reward = payment?.reward ?? null\n\tconst defaultEmail = payment?.payoutEmail ?? null\n\tconst tasksTotal = payment?.tasksTotal ?? 0\n\n\tconst head = document.createElement('div')\n\thead.className = 'head'\n\tconst lede = reward\n\t\t? `We have your recording. Confirm where to send your ${escapeHtml(reward)} and the team will review it shortly.`\n\t\t: 'We have your recording. Thanks for taking the time to walk us through it.'\n\thead.innerHTML = `\n\t\t<div class=\"check ok\" aria-hidden=\"true\">${TICK_ICON_SVG}</div>\n\t\t<h2>You're done.</h2>\n\t\t<p class=\"lede\">${lede}</p>\n\t\t${tasksTotal > 0\n\t\t\t? checksList([\n\t\t\t\t\t{ label: tasksTotal === 1 ? '1 task completed' : `All ${tasksTotal} tasks completed`, done: true },\n\t\t\t\t\t{ label: 'Voice recording captured', done: true },\n\t\t\t\t\t{ label: 'Screen replay uploaded', done: true },\n\t\t\t\t])\n\t\t\t: checksList([\n\t\t\t\t\t{ label: 'Voice recording captured', done: true },\n\t\t\t\t\t{ label: 'Screen replay uploaded', done: true },\n\t\t\t\t])}\n\t`\n\tcard.appendChild(head)\n\n\t// If we have no payment data (older server) skip payout entirely and go\n\t// straight to the wrap-up note.\n\tif (!payment) {\n\t\tappendNoteSection(card, opts, 'Your session was saved. Anything you would add?')\n\t\treturn\n\t}\n\n\trenderPayout(card, opts, reward, defaultEmail)\n}\n\n// Payout capture: one-tap default to the sign-up email, with a quieter expander\n// to use a different email. Progressive disclosure (the default path is not a form).\nfunction renderPayout(card: HTMLElement, opts: ThanksOptions, reward: string | null, defaultEmail: string | null): void {\n\tconst wrap = document.createElement('div')\n\twrap.className = 'payout'\n\n\tconst rewardLabel = reward ?? 'my reward'\n\tconst haveDefault = !!defaultEmail && isValidEmail(defaultEmail)\n\n\twrap.innerHTML = `\n\t\t<p class=\"payout-q\">Where should we send ${escapeHtml(reward ?? 'your reward')}?</p>\n\t\t<button type=\"button\" class=\"pay-primary\" ${haveDefault ? '' : 'hidden'}>\n\t\t\tSend <span class=\"amt\">${escapeHtml(rewardLabel)}</span>${haveDefault ? ` to ${escapeHtml(defaultEmail as string)}` : ''}\n\t\t</button>\n\t\t<button type=\"button\" class=\"pay-alt\">${haveDefault ? 'Use a different email' : 'Add your payout email'}</button>\n\t\t<div class=\"pay-edit\" ${haveDefault ? 'hidden' : ''}>\n\t\t\t<label class=\"pay-label\" for=\"usero-payout-email\">Payout email</label>\n\t\t\t<input id=\"usero-payout-email\" class=\"pay-input\" type=\"email\" inputmode=\"email\"\n\t\t\t\tautocomplete=\"email\" placeholder=\"you@example.com\" value=\"${haveDefault ? '' : escapeHtml(defaultEmail ?? '')}\" />\n\t\t</div>\n\t\t<p class=\"pay-eta\">Reward arrives within about 2 days of the team reviewing it.</p>\n\t`\n\tcard.appendChild(wrap)\n\n\tconst primary = wrap.querySelector<HTMLButtonElement>('.pay-primary')\n\tconst altLink = wrap.querySelector<HTMLButtonElement>('.pay-alt')\n\tconst editBox = wrap.querySelector<HTMLElement>('.pay-edit')\n\tconst emailInput = wrap.querySelector<HTMLInputElement>('.pay-input')\n\tif (!primary || !altLink || !editBox || !emailInput) return\n\n\tconst confirm = async (destination: string | null): Promise<void> => {\n\t\tprimary.disabled = true\n\t\taltLink.style.pointerEvents = 'none'\n\t\tconst ok = await opts.onPayout(destination)\n\t\t// Whatever the network outcome, the session is payable (server defaults to\n\t\t// the sign-up email). Move the participant forward rather than trapping them.\n\t\twrap.remove()\n\t\tconst confirmedTo = destination ?? defaultEmail\n\t\tconst sentMsg = confirmedTo\n\t\t\t? `${reward ? `${reward} is` : \"Your reward is\"} set to go to ${confirmedTo}.`\n\t\t\t: 'Your reward is on its way.'\n\t\tconst note = ok ? sentMsg : `${sentMsg} (We will retry sending the details.)`\n\t\tappendNoteSection(card, opts, `${note} Anything you would add before you go?`)\n\t}\n\n\t// One-tap default path.\n\tprimary.addEventListener('click', () => { void confirm(null) })\n\n\t// Expander: reveal the email field, focus it, submit on Enter.\n\tconst openEditor = (): void => {\n\t\tprimary.hidden = true\n\t\taltLink.hidden = true\n\t\teditBox.hidden = false\n\t\t// Append a confirm button under the input on first open.\n\t\tif (!editBox.querySelector('.pay-confirm')) {\n\t\t\tconst btn = document.createElement('button')\n\t\t\tbtn.type = 'button'\n\t\t\tbtn.className = 'pay-primary pay-confirm'\n\t\t\tbtn.style.marginTop = '12px'\n\t\t\tbtn.textContent = reward ? `Send ${reward} here` : 'Use this email'\n\t\t\teditBox.appendChild(btn)\n\t\t\tbtn.addEventListener('click', () => void submitEmail())\n\t\t}\n\t\twindow.requestAnimationFrame(() => emailInput.focus({ preventScroll: true }))\n\t}\n\n\tconst submitEmail = async (): Promise<void> => {\n\t\tconst value = emailInput.value.trim().toLowerCase()\n\t\tif (!isValidEmail(value)) {\n\t\t\temailInput.focus()\n\t\t\temailInput.style.borderColor = '#dc2626'\n\t\t\treturn\n\t\t}\n\t\tawait confirm(value)\n\t}\n\n\taltLink.addEventListener('click', openEditor)\n\temailInput.addEventListener('input', () => { emailInput.style.borderColor = '' })\n\temailInput.addEventListener('keydown', e => {\n\t\tif (e.key === 'Enter') { e.preventDefault(); void submitEmail() }\n\t})\n}\n\nfunction renderEndedEarly(card: HTMLElement, opts: ThanksOptions): void {\n\tconst payment = opts.payment\n\tconst done = payment?.tasksDone ?? 0\n\tconst total = payment?.tasksTotal ?? 0\n\tconst reward = payment?.reward ?? null\n\n\tconst head = document.createElement('div')\n\thead.className = 'head'\n\tconst lede = total > 0\n\t\t? `We saw ${done} of ${total} ${total === 1 ? 'task' : 'tasks'} finished. No worries, you can pick up right where you left off.`\n\t\t: 'It looks like the session ended before you finished. No worries, you can pick up where you left off.'\n\thead.innerHTML = `\n\t\t<div class=\"check early\" aria-hidden=\"true\">${CLOCK_ICON_SVG}</div>\n\t\t<h2>Looks like you stopped early</h2>\n\t\t<p class=\"lede\">${lede}</p>\n\t`\n\tcard.appendChild(head)\n\n\t// Per-task progress when we know the counts: done rows ticked, the rest hollow.\n\tif (total > 0) {\n\t\tconst rows: Array<{ label: string; done: boolean }> = []\n\t\tfor (let i = 0; i < total; i += 1) {\n\t\t\trows.push({ label: `Task ${i + 1}`, done: i < done })\n\t\t}\n\t\tconst list = document.createElement('div')\n\t\tlist.innerHTML = checksList(rows)\n\t\tconst ul = list.firstElementChild\n\t\tif (ul) card.appendChild(ul)\n\t}\n\n\tconst note = document.createElement('div')\n\tnote.className = 'early-note'\n\tnote.innerHTML = `${SPARK_ICON_SVG}<span><strong style=\"font-weight:600\">Resume the test.</strong> ${\n\t\treward ? `Your ${escapeHtml(reward)} reward unlocks` : 'The reward unlocks'\n\t} once all ${total > 0 ? total : 'the'} ${total === 1 ? 'task is' : 'tasks are'} done.</span>`\n\tcard.appendChild(note)\n\n\tconst actions = document.createElement('div')\n\tactions.className = 'early-actions'\n\tconst resume = document.createElement('button')\n\tresume.type = 'button'\n\tresume.className = 'resume-btn'\n\tresume.textContent = 'Resume where I left off'\n\tconst exit = document.createElement('button')\n\texit.type = 'button'\n\texit.className = 'exit-btn'\n\texit.textContent = \"Thanks for trying. No reward this time since the tasks weren't finished.\"\n\tactions.appendChild(resume)\n\tactions.appendChild(exit)\n\tcard.appendChild(actions)\n\n\tresume.addEventListener('click', () => {\n\t\tconst overlay = card.closest('.thanks')\n\t\tif (overlay instanceof HTMLElement) overlay.remove()\n\t\topts.onResume()\n\t})\n\texit.addEventListener('click', () => {\n\t\tcard.innerHTML = ''\n\t\tconst sent = document.createElement('p')\n\t\tsent.className = 'end-sent'\n\t\tsent.textContent = 'Thanks for giving it a go. You can close this tab now.'\n\t\tcard.appendChild(sent)\n\t})\n}\n\n// The wrap-up note section, shared by the complete path (after payout) and the\n// older-server fallback. Mirrors the prior behaviour: Cmd/Ctrl+Enter to send,\n// retry on failure, skip allowed.\nfunction appendNoteSection(card: HTMLElement, opts: ThanksOptions, prompt: string): void {\n\tconst section = document.createElement('div')\n\tsection.className = 'note-section'\n\n\tconst form = document.createElement('form')\n\tform.noValidate = true\n\tform.innerHTML = `\n\t\t<label class=\"end-label\" for=\"usero-end-note\">${escapeHtml(prompt)}</label>\n\t\t<textarea\n\t\t\tid=\"usero-end-note\"\n\t\t\tclass=\"end-textarea\"\n\t\t\trows=\"3\"\n\t\t\tplaceholder=\"Confusing bits, things you liked, what you'd change...\"\n\t\t></textarea>\n\t\t<div class=\"end-actions\">\n\t\t\t<button type=\"button\" class=\"skip\">Skip</button>\n\t\t\t<button type=\"submit\" class=\"primary\">Send feedback</button>\n\t\t</div>\n\t\t<p class=\"end-hint\">Cmd or Ctrl plus Enter to send. Either button is fine.</p>\n\t`\n\tsection.appendChild(form)\n\tcard.appendChild(section)\n\n\tconst ta = form.querySelector<HTMLTextAreaElement>('#usero-end-note')\n\tconst skipBtn = form.querySelector<HTMLButtonElement>('button.skip')\n\tif (!ta || !skipBtn) return\n\n\tconst swapToSent = (message: string): void => {\n\t\tsection.remove()\n\t\tconst sent = document.createElement('p')\n\t\tsent.className = 'end-sent'\n\t\tsent.textContent = message\n\t\tcard.appendChild(sent)\n\t}\n\n\tconst ERROR_CLASS = 'end-error'\n\tconst showError = (message: string): void => {\n\t\tconst prior = form.querySelector(`.${ERROR_CLASS}`)\n\t\tif (prior) prior.remove()\n\t\tconst err = document.createElement('p')\n\t\terr.className = ERROR_CLASS\n\t\terr.textContent = message\n\t\terr.setAttribute('role', 'alert')\n\t\terr.style.cssText = 'margin:10px 0 0;font-size:12.5px;color:#b91c1c;text-align:center;'\n\t\tform.appendChild(err)\n\t}\n\n\tconst submit = async (): Promise<void> => {\n\t\tconst text = ta.value.trim()\n\t\tta.disabled = true\n\t\tskipBtn.disabled = true\n\t\tconst submitBtn = form.querySelector<HTMLButtonElement>('button.primary')\n\t\tif (submitBtn) submitBtn.disabled = true\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tawait Promise.race([\n\t\t\t\t\tPromise.resolve(opts.onSubmitNote(text)),\n\t\t\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\t\t\twindow.setTimeout(() => reject(new Error('timeout')), 30000)\n\t\t\t\t\t}),\n\t\t\t\t])\n\t\t\t\tswapToSent('Thanks. You can close this tab.')\n\t\t\t} catch {\n\t\t\t\tta.disabled = false\n\t\t\t\tskipBtn.disabled = false\n\t\t\t\tif (submitBtn) submitBtn.disabled = false\n\t\t\t\tshowError(\"Couldn't save your note. Try again?\")\n\t\t\t}\n\t\t} else {\n\t\t\topts.onSkip()\n\t\t\tswapToSent('All set. You can close this tab.')\n\t\t}\n\t}\n\n\tform.addEventListener('submit', e => { e.preventDefault(); void submit() })\n\tskipBtn.addEventListener('click', () => { ta.value = ''; void submit() })\n\tta.addEventListener('keydown', e => {\n\t\tif ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n\t\t\te.preventDefault()\n\t\t\tvoid submit()\n\t\t}\n\t})\n\n\twindow.requestAnimationFrame(() => { ta.focus({ preventScroll: true }) })\n}\n\nfunction parseTasks(raw: unknown): UserTestTask[] {\n\tif (!Array.isArray(raw)) return []\n\tconst out = raw.flatMap((item: unknown): UserTestTask[] => {\n\t\tconst t = item as { id?: unknown; prompt?: unknown; sortOrder?: unknown }\n\t\tif (!t || typeof t.id !== 'string' || typeof t.prompt !== 'string' || typeof t.sortOrder !== 'number') return []\n\t\treturn [{ id: t.id, prompt: t.prompt, sortOrder: t.sortOrder }]\n\t})\n\tout.sort((a, b) => a.sortOrder - b.sortOrder)\n\treturn out\n}\n\nasync function createSession(\n\tapiUrl: string,\n\tslug: string,\n\ttesterName: string | undefined,\n): Promise<{ sessionId: string; clientId: string; tasks: UserTestTask[] } | null> {\n\ttry {\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ slug, ...(testerName ? { testerName } : {}) }),\n\t\t})\n\t\tif (!res.ok) return null\n\t\tconst json = (await res.json()) as { sessionId?: unknown; clientId?: unknown; tasks?: unknown }\n\t\tif (typeof json.sessionId !== 'string' || typeof json.clientId !== 'string') return null\n\t\treturn { sessionId: json.sessionId, clientId: json.clientId, tasks: parseTasks(json.tasks) }\n\t} catch {\n\t\treturn null\n\t}\n}\n\n// Adopt an existing session the entry screen already created (carried via the\n// `uts` URL param). GET the clientId + tasks for it; we do NOT create a new\n// session. Returns null on any failure so the caller can surface the error\n// state (we deliberately do NOT silently fall back to createSession here: a\n// present-but-unresolvable uts means something is wrong, and creating a second\n// anonymous session is exactly the double-session bug we're avoiding).\nasync function adoptSession(\n\tapiUrl: string,\n\tsessionId: string,\n): Promise<{ sessionId: string; clientId: string; tasks: UserTestTask[] } | null> {\n\ttry {\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/adopt`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\t\tif (!res.ok) return null\n\t\tconst json = (await res.json()) as { sessionId?: unknown; clientId?: unknown; tasks?: unknown }\n\t\tif (typeof json.sessionId !== 'string' || typeof json.clientId !== 'string') return null\n\t\treturn { sessionId: json.sessionId, clientId: json.clientId, tasks: parseTasks(json.tasks) }\n\t} catch {\n\t\treturn null\n\t}\n}\n\ninterface FinaliseNote {\n\tatMs: number\n\ttext: string\n}\n\n// Participant-facing payment summary the finalise endpoint returns on the FIRST\n// call. Drives the finished screen: `qualified` picks the complete vs ended-early\n// layout, `reward` is the formatted headline (e.g. \"$15\"), `payoutEmail` seeds the\n// one-tap payout default, tasksDone/tasksTotal drive the ended-early missing line.\n// Every field is optional so an older server (no `payment` block) degrades to the\n// neutral \"thanks, saved\" screen rather than throwing.\ninterface PaymentSummary {\n\tqualified: boolean\n\treward: string | null\n\tpayoutEmail: string | null\n\ttasksDone: number\n\ttasksTotal: number\n}\n\ninterface FinaliseResult {\n\tok: boolean\n\t// Only present on the first finalise call (the server computes it once).\n\tpayment: PaymentSummary | null\n}\n\nfunction parsePaymentSummary(raw: unknown): PaymentSummary | null {\n\tif (typeof raw !== 'object' || raw === null) return null\n\tconst p = raw as {\n\t\tqualified?: unknown\n\t\treward?: unknown\n\t\tpayoutEmail?: unknown\n\t\ttasksDone?: unknown\n\t\ttasksTotal?: unknown\n\t}\n\tif (typeof p.qualified !== 'boolean') return null\n\treturn {\n\t\tqualified: p.qualified,\n\t\treward: typeof p.reward === 'string' ? p.reward : null,\n\t\tpayoutEmail: typeof p.payoutEmail === 'string' ? p.payoutEmail : null,\n\t\ttasksDone: typeof p.tasksDone === 'number' ? p.tasksDone : 0,\n\t\ttasksTotal: typeof p.tasksTotal === 'number' ? p.tasksTotal : 0,\n\t}\n}\n\nasync function finaliseSession(\n\tapiUrl: string,\n\tsessionId: string,\n\tdurationSeconds: number,\n\textras: {\n\t\tmutedSegments?: MutedSegment[]\n\t\tendNote?: string | null\n\t\tnotes?: FinaliseNote[]\n\t\t// Replay linkage. sdkSessionId is the primary, always-available key:\n\t\t// the server resolves the SessionReplay by (clientId + sdkSessionId)\n\t\t// and sets UserTestSession.sessionReplayId. replayOffsetMs is the\n\t\t// offset captured at session start, only present when replay was\n\t\t// active. Both optional so older servers tolerate their absence and a\n\t\t// test with no replay still finalises cleanly.\n\t\tsdkSessionId?: string\n\t\treplayOffsetMs?: number\n\t} = {},\n): Promise<FinaliseResult> {\n\ttry {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tdurationSeconds: Math.max(0, Math.round(durationSeconds)),\n\t\t}\n\t\tif (extras.mutedSegments && extras.mutedSegments.length > 0) {\n\t\t\tbody.mutedSegments = extras.mutedSegments\n\t\t}\n\t\tconst trimmedEndNote = extras.endNote?.trim()\n\t\tif (trimmedEndNote) body.endNote = trimmedEndNote\n\t\tif (extras.notes && extras.notes.length > 0) {\n\t\t\t// Server caps at 200; trim defensively here too.\n\t\t\tbody.notes = extras.notes.slice(0, 200).map(n => ({\n\t\t\t\tatMs: Math.max(0, Math.round(n.atMs)),\n\t\t\t\ttext: n.text,\n\t\t\t}))\n\t\t}\n\t\tif (extras.sdkSessionId) body.sdkSessionId = extras.sdkSessionId\n\t\tif (typeof extras.replayOffsetMs === 'number') {\n\t\t\tbody.replayOffsetMs = Math.max(0, Math.round(extras.replayOffsetMs))\n\t\t}\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/finalise`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tkeepalive: true,\n\t\t})\n\t\tif (!res.ok) return { ok: false, payment: null }\n\t\tlet payment: PaymentSummary | null = null\n\t\ttry {\n\t\t\tconst json = (await res.json()) as { payment?: unknown }\n\t\t\tpayment = parsePaymentSummary(json.payment)\n\t\t} catch {\n\t\t\t// Older server or non-JSON body: degrade to neutral thanks screen.\n\t\t}\n\t\treturn { ok: true, payment }\n\t} catch {\n\t\treturn { ok: false, payment: null }\n\t}\n}\n\n// POST the participant's payout destination to the SaaS side. Best-effort with a\n// single retry; the destination defaults server-side to the testerEmail when we\n// send only `method`, so a dropped call still leaves a payable session. Returns\n// ok so the UI can confirm or surface a soft error.\nasync function postPayout(\n\tapiUrl: string,\n\tsessionId: string,\n\tdestination: string | null,\n\tlogger: PluginContext['logger'],\n): Promise<boolean> {\n\tconst url = `${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/payout`\n\tconst body: Record<string, unknown> = { method: 'email' }\n\tif (destination) body.destination = destination\n\tfor (let attempt = 0; attempt < 2; attempt += 1) {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\tkeepalive: true,\n\t\t\t})\n\t\t\tif (res.ok) return true\n\t\t\t// 4xx won't improve on retry (bad email, etc.).\n\t\t\tif (res.status >= 400 && res.status < 500) {\n\t\t\t\tlogger.warn(`payout rejected with ${res.status}`)\n\t\t\t\treturn false\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlogger.warn(`payout attempt ${attempt + 1} failed`, err)\n\t\t}\n\t\tawait new Promise(resolve => setTimeout(resolve, 400 + Math.floor(Math.random() * 200)))\n\t}\n\treturn false\n}\n\ninterface PostNoteResult {\n\tok: boolean\n\tid?: string\n\ttransient: boolean // true on network error / 5xx — eligible for retry\n}\n\nasync function postNoteOnce(\n\tapiUrl: string,\n\tsessionId: string,\n\tatMs: number,\n\ttext: string,\n\tlogger: PluginContext['logger'],\n): Promise<PostNoteResult> {\n\ttry {\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/notes`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ atMs: Math.max(0, Math.round(atMs)), text }),\n\t\t\tkeepalive: true,\n\t\t})\n\t\tif (!res.ok) {\n\t\t\tlogger.warn(`note POST rejected with ${res.status}`)\n\t\t\treturn { ok: false, transient: res.status >= 500 || res.status === 408 || res.status === 429 }\n\t\t}\n\t\t// Best-effort id extraction; failures here don't matter for ack.\n\t\tlet id: string | undefined\n\t\ttry {\n\t\t\tconst json = (await res.json()) as { id?: unknown }\n\t\t\tif (typeof json.id === 'string') id = json.id\n\t\t} catch { /* ignore */ }\n\t\treturn { ok: true, id, transient: false }\n\t} catch (err) {\n\t\tlogger.warn('note POST failed', err)\n\t\treturn { ok: false, transient: true }\n\t}\n}\n\n// One immediate retry on transient errors. If still failing, defer to\n// finalise batching via the un-acked notes channel.\nasync function postNoteWithRetry(\n\tapiUrl: string,\n\tsessionId: string,\n\tatMs: number,\n\ttext: string,\n\tlogger: PluginContext['logger'],\n): Promise<PostNoteResult> {\n\tconst first = await postNoteOnce(apiUrl, sessionId, atMs, text, logger)\n\tif (first.ok || !first.transient) return first\n\tawait new Promise(resolve => setTimeout(resolve, 400 + Math.floor(Math.random() * 200)))\n\treturn postNoteOnce(apiUrl, sessionId, atMs, text, logger)\n}\n\n\nasync function flushPendingFromIdb(store: RecorderStore, ctx: PluginContext): Promise<void> {\n\tif (!store.sessionId) return\n\tconst pending = await idbListChunks(store.sessionId)\n\tfor (const chunk of pending) {\n\t\tconst ok = await uploadChunkWithRetry(chunk.apiUrl, chunk.sessionId, chunk.chunkIndex, chunk.blob, ctx.logger, 3)\n\t\tif (ok) await idbDeleteChunk(chunk.id)\n\t}\n}\n\nfunction enqueueChunk(store: RecorderStore, ctx: PluginContext, blob: Blob): void {\n\tif (store.cancelled || !store.sessionId || blob.size === 0) return\n\tconst index = store.chunkIndex\n\tstore.chunkIndex += 1\n\tstore.pendingUploads += 1\n\tconst sessionId = store.sessionId\n\tconst apiUrl = store.options.apiUrl\n\n\tstore.uploadQueue = store.uploadQueue.then(async () => {\n\t\tconst ok = await uploadChunkWithRetry(apiUrl, sessionId, index, blob, ctx.logger)\n\t\tif (!ok) {\n\t\t\tctx.logger.warn(`chunk ${index} stashed for offline retry`)\n\t\t\tawait idbStashChunk({\n\t\t\t\tid: `${sessionId}:${index}:${Date.now()}`,\n\t\t\t\tsessionId,\n\t\t\t\tapiUrl,\n\t\t\t\tchunkIndex: index,\n\t\t\t\tblob,\n\t\t\t\tcreatedAt: Date.now(),\n\t\t\t})\n\t\t}\n\t\tstore.pendingUploads -= 1\n\t})\n}\n\n// ---------------------------------------------------------------------------\n// Silent-microphone guard\n//\n// A dead mic, or a virtual audio input device that macOS hands Chrome (e.g.\n// \"Background Music\", a Zoom/Teams virtual mic), delivers digital silence.\n// getUserMedia succeeds, MediaRecorder records, and 15 minutes of nothing\n// reaches the researcher with no warning. We DETECT a silent input stream and\n// WARN the participant (non-blocking: recording always continues).\n//\n// Detection runs a Web Audio AnalyserNode over the live track and computes RMS\n// in dBFS over a short window. The decision is a pure function so it can be\n// unit-tested without a real AudioContext.\n\n// Threshold rationale (dBFS, full-scale = 0):\n// - True failure is essentially digital silence: every sample ~0, so RMS is\n// -Infinity (or, with analyser float error, well below -80 dB).\n// - Confirmed-real speech in our captured data sits around -36 dB RMS.\n// - Quiet-but-present speech (a soft talker) sits around -40 to -50 dB.\n// We set the bar at -60 dB: only treat the stream as silent when RMS is\n// effectively zero. A -50 dB quiet voice is a full 10 dB ABOVE the line and\n// will never trip it, so we never falsely stop a real (quiet) participant.\n// This is deliberately conservative per the product decision: warn, never\n// block, and never false-positive on a real voice.\nconst SILENCE_RMS_DB_THRESHOLD = -60\n\n// dBFS for a fully-silent (all-zero) window is -Infinity. Floor it to a finite\n// value so the pure decision function stays total and testable.\nconst SILENCE_FLOOR_DB = -100\n\n// How long the analyser must read continuous silence before we surface the\n// warning. ~1.8s at record start and as the sustained-silence window during\n// recording. Long enough that a natural pause between sentences (which dips\n// toward the floor for a fraction of a second) never trips it; short enough\n// that a dead device is flagged almost immediately.\nconst SILENCE_SUSTAINED_MS = 1800\n\n// How often the monitor samples the analyser.\nconst SILENCE_POLL_MS = 250\n\n// Compute RMS in dBFS from normalized float time-domain samples (each in\n// [-1, 1], as produced by AnalyserNode.getFloatTimeDomainData). Returns a\n// finite dB value floored at SILENCE_FLOOR_DB so an all-zero window doesn't\n// yield -Infinity. Pure, no Web Audio needed: unit-tested directly.\nexport function rmsDbFromSamples(samples: Float32Array | number[]): number {\n\tconst n = samples.length\n\tif (n === 0) return SILENCE_FLOOR_DB\n\tlet sumSquares = 0\n\tfor (let i = 0; i < n; i += 1) {\n\t\tconst s = samples[i] ?? 0\n\t\tsumSquares += s * s\n\t}\n\tconst rms = Math.sqrt(sumSquares / n)\n\tif (rms <= 0) return SILENCE_FLOOR_DB\n\tconst db = 20 * Math.log10(rms)\n\treturn db < SILENCE_FLOOR_DB ? SILENCE_FLOOR_DB : db\n}\n\n// Pure silence decision. Accepts EITHER an already-computed RMS dB value (a\n// number) OR a sample window (array). True only when the level is at/below the\n// conservative silence threshold. A real voice, even a quiet one (-40 to\n// -50 dB), is comfortably above the line and returns false.\nexport function isStreamSilent(input: number | Float32Array | number[]): boolean {\n\tconst rmsDb = typeof input === 'number' ? input : rmsDbFromSamples(input)\n\treturn rmsDb <= SILENCE_RMS_DB_THRESHOLD\n}\n\n// Live monitor: wires an AnalyserNode onto the stream and polls it. Calls\n// `onChange(silent)` only on transitions (silent <-> audible), debounced by\n// SILENCE_SUSTAINED_MS so a brief between-words dip never flips the pill.\n// Returns a teardown that closes the AudioContext and disconnects nodes.\ninterface SilenceMonitor {\n\tstop(): void\n}\n\nfunction startSilenceMonitor(stream: MediaStream, onChange: (silent: boolean) => void, logger: PluginContext['logger']): SilenceMonitor | null {\n\tconst Ctor: typeof AudioContext | undefined =\n\t\ttypeof window !== 'undefined'\n\t\t\t? (window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext)\n\t\t\t: undefined\n\tif (!Ctor) return null\n\n\tlet audioCtx: AudioContext\n\tlet source: MediaStreamAudioSourceNode\n\tlet analyser: AnalyserNode\n\ttry {\n\t\taudioCtx = new Ctor()\n\t\tsource = audioCtx.createMediaStreamSource(stream)\n\t\tanalyser = audioCtx.createAnalyser()\n\t\tanalyser.fftSize = 2048\n\t\t// We do not connect the analyser to audioCtx.destination: we only READ\n\t\t// the track, never play it back (that would echo the participant's own\n\t\t// mic into their speakers).\n\t\tsource.connect(analyser)\n\t} catch (err) {\n\t\tlogger.warn('silence monitor: failed to attach analyser', err)\n\t\treturn null\n\t}\n\n\tconst buffer = new Float32Array(analyser.fftSize)\n\t// Reported state (what the pill currently shows). Starts audible; we only\n\t// flip to silent after SILENCE_SUSTAINED_MS of continuous silence.\n\tlet reportedSilent = false\n\t// Timestamp the CURRENT run of same-classification readings began.\n\tlet runStartedAt = Date.now()\n\tlet lastRaw = false\n\tlet intervalId: ReturnType<typeof setInterval> | null = null\n\n\tconst tick = (): void => {\n\t\t// getFloatTimeDomainData is widely supported; guard for older engines.\n\t\ttry {\n\t\t\tanalyser.getFloatTimeDomainData(buffer)\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tconst rawSilent = isStreamSilent(buffer)\n\t\tconst now = Date.now()\n\t\tif (rawSilent !== lastRaw) {\n\t\t\tlastRaw = rawSilent\n\t\t\trunStartedAt = now\n\t\t}\n\t\t// Only commit a state change once the raw classification has held for\n\t\t// the sustained window. Going audible clears the warning immediately\n\t\t// once sustained, going silent raises it once sustained.\n\t\tif (rawSilent !== reportedSilent && now - runStartedAt >= SILENCE_SUSTAINED_MS) {\n\t\t\treportedSilent = rawSilent\n\t\t\tonChange(reportedSilent)\n\t\t}\n\t}\n\n\tintervalId = setInterval(tick, SILENCE_POLL_MS)\n\n\treturn {\n\t\tstop(): void {\n\t\t\tif (intervalId !== null) {\n\t\t\t\tclearInterval(intervalId)\n\t\t\t\tintervalId = null\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tsource.disconnect()\n\t\t\t\tanalyser.disconnect()\n\t\t\t} catch {\n\t\t\t\t// nodes may already be torn down\n\t\t\t}\n\t\t\t// close() returns a promise; failures here are harmless (context may\n\t\t\t// already be closing on page unload).\n\t\t\ttry {\n\t\t\t\tvoid audioCtx.close()\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t},\n\t}\n}\n\nasync function startRecording(store: RecorderStore, ctx: PluginContext): Promise<void> {\n\t// Re-entrant: the failed chip re-invokes this to retry. Reset to the\n\t// pending state so the chip shows \"connecting\" again during the attempt.\n\tstore.micAcquiring = true\n\tstore.micFailReason = null\n\t// A retry tears down a prior monitor and clears the silent flag so the\n\t// fresh attempt starts from a clean state.\n\tif (store.silenceMonitor) {\n\t\tstore.silenceMonitor.stop()\n\t\tstore.silenceMonitor = null\n\t}\n\tstore.micSilent = false\n\trenderIndicatorState(store)\n\tif (!isMediaRecorderSupported()) {\n\t\tctx.logger.warn('MediaRecorder not supported, continuing without audio')\n\t\tstore.micAcquiring = false\n\t\tstore.micFailReason = 'unsupported'\n\t\tstore.indicatorState = 'no-audio'\n\t\trenderIndicatorState(store)\n\t\treturn\n\t}\n\tlet stream: MediaStream\n\ttry {\n\t\tstream = await navigator.mediaDevices.getUserMedia({ audio: true })\n\t} catch (err) {\n\t\tctx.logger.warn('mic permission denied or unavailable', err)\n\t\tstore.micAcquiring = false\n\t\t// Distinguish denied (blocked) from no-device (not-found) for copy.\n\t\tconst name = err instanceof Error ? err.name : ''\n\t\tstore.micFailReason = name === 'NotFoundError' || name === 'DevicesNotFoundError' ? 'not-found' : 'blocked'\n\t\tstore.indicatorState = 'no-audio'\n\t\trenderIndicatorState(store)\n\t\treturn\n\t}\n\tstore.stream = stream\n\tstore.hasMicPermission = true\n\tstore.micAcquiring = false\n\tstore.micFailReason = null\n\tconst mimeType = pickMimeType()\n\tlet recorder: MediaRecorder\n\ttry {\n\t\trecorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream)\n\t} catch (err) {\n\t\tctx.logger.error('MediaRecorder construction failed', err)\n\t\tstream.getTracks().forEach(t => t.stop())\n\t\tstore.stream = null\n\t\tstore.hasMicPermission = false\n\t\tstore.micAcquiring = false\n\t\tstore.micFailReason = 'unsupported'\n\t\tstore.indicatorState = 'no-audio'\n\t\trenderIndicatorState(store)\n\t\treturn\n\t}\n\tstore.recorder = recorder\n\trecorder.addEventListener('dataavailable', event => {\n\t\tif (event.data && event.data.size > 0) {\n\t\t\tenqueueChunk(store, ctx, event.data)\n\t\t}\n\t})\n\trecorder.addEventListener('error', event => {\n\t\tctx.logger.error('MediaRecorder error', event)\n\t})\n\t// `timeslice` makes the recorder emit a self-contained chunk every N ms.\n\trecorder.start(store.options.chunkSeconds * 1000)\n\t// On a successful (re)acquire, restore the live recording indicator. A\n\t// prior failed attempt left indicatorState at 'no-audio' (steady amber\n\t// dot); now that audio is live the dot should pulse red again.\n\tif (store.indicatorState === 'no-audio') {\n\t\tstore.indicatorState = 'recording'\n\t}\n\trenderIndicatorState(store)\n\n\t// Start the silent-mic guard over the live track. Detects a dead or virtual\n\t// silent input at record start and keeps checking for a mid-session mic\n\t// death. Warning only; recording is never gated on it. When the participant\n\t// has muted themselves the track is intentionally silent, so we suppress the\n\t// warning in that case (see the onChange guard).\n\tconst monitor = startSilenceMonitor(\n\t\tstream,\n\t\tsilent => {\n\t\t\t// Ignore the analyser while the participant has deliberately muted:\n\t\t\t// a muted track reads as silence by design, that's not a fault.\n\t\t\tconst effectiveSilent = silent && !store.muted\n\t\t\tif (store.micSilent === effectiveSilent) return\n\t\t\tstore.micSilent = effectiveSilent\n\t\t\trenderIndicatorState(store)\n\t\t},\n\t\tctx.logger,\n\t)\n\tstore.silenceMonitor = monitor\n}\n\nfunction toggleMute(store: RecorderStore): boolean {\n\tif (!store.stream || !store.hasMicPermission) return false\n\tconst tracks = store.stream.getAudioTracks()\n\tif (tracks.length === 0) return false\n\tconst nowMs = Date.now() - store.startedAt\n\tif (!store.muted) {\n\t\t// Going muted: disable each audio track. MediaRecorder keeps running;\n\t\t// the disabled track produces silence in the resulting WebM. We do NOT\n\t\t// pause the recorder so the single-stream lifecycle stays simple.\n\t\tfor (const t of tracks) t.enabled = false\n\t\tstore.muted = true\n\t\tstore.mutedSinceMs = nowMs\n\t} else {\n\t\t// Coming back: close the muted segment, re-enable.\n\t\tconst startMs = store.mutedSinceMs ?? nowMs\n\t\tif (nowMs > startMs) {\n\t\t\tstore.mutedSegments.push({ startMs, endMs: nowMs })\n\t\t}\n\t\tstore.mutedSinceMs = null\n\t\tstore.muted = false\n\t\tfor (const t of tracks) t.enabled = true\n\t}\n\treturn true\n}\n\nfunction flushMuteIfActive(store: RecorderStore): void {\n\tif (!store.muted || store.mutedSinceMs === null) return\n\tconst nowMs = Date.now() - store.startedAt\n\tif (nowMs > store.mutedSinceMs) {\n\t\tstore.mutedSegments.push({ startMs: store.mutedSinceMs, endMs: nowMs })\n\t}\n\tstore.mutedSinceMs = null\n}\n\nfunction stopRecording(store: RecorderStore): void {\n\tif (store.silenceMonitor) {\n\t\tstore.silenceMonitor.stop()\n\t\tstore.silenceMonitor = null\n\t}\n\tstore.micSilent = false\n\tconst recorder = store.recorder\n\tif (recorder && recorder.state !== 'inactive') {\n\t\ttry {\n\t\t\trecorder.requestData()\n\t\t} catch {\n\t\t\t// requestData throws if state is invalid; ignore.\n\t\t}\n\t\ttry {\n\t\t\trecorder.stop()\n\t\t} catch {\n\t\t\t// already stopped\n\t\t}\n\t}\n\tstore.recorder = null\n\tif (store.stream) {\n\t\tstore.stream.getTracks().forEach(t => t.stop())\n\t\tstore.stream = null\n\t}\n}\n\nasync function finishFlow(store: RecorderStore, ctx: PluginContext, opts: { showThanks: boolean }): Promise<void> {\n\tif (store.cancelled) return\n\t// Short-circuit re-entry. The pagehide handler and the manual Finish click\n\t// can race; server is also idempotent on a second finalise call, but doing\n\t// the local work twice (flushing mute, draining queues, etc.) is wasted.\n\tif (store.finishFlowRan) return\n\tif (store.indicatorState === 'finishing' || store.indicatorState === 'done') return\n\tstore.finishFlowRan = true\n\tstore.indicatorState = 'finishing'\n\tflushMuteIfActive(store)\n\trenderIndicatorState(store)\n\n\tstopRecording(store)\n\t// Wait for the queued uploads to drain. Each upload already has its own\n\t// retry/backoff; this just lets them finish before finalise fires.\n\tawait store.uploadQueue\n\tawait flushPendingFromIdb(store, ctx)\n\n\tconst durationSeconds = (Date.now() - store.startedAt) / 1000\n\t// Replay linkage, computed once and sent on every finalise call for this\n\t// session. sdkSessionId is the core-owned per-tab id (the primary key the\n\t// server uses to resolve the SessionReplay); replayOffsetMs is the offset\n\t// captured at session start, only set when replay was active. Both degrade\n\t// gracefully to absent. Sending it on the second (end-note) finalise too\n\t// is harmless: the server stores idempotently.\n\tconst replayLinkage: { sdkSessionId?: string; replayOffsetMs?: number } = {}\n\tconst linkageSdkSessionId = ctx.getSdkSessionId ? ctx.getSdkSessionId() : undefined\n\tif (linkageSdkSessionId) replayLinkage.sdkSessionId = linkageSdkSessionId\n\tif (store.replayOffsetAtStartMs !== null) {\n\t\treplayLinkage.replayOffsetMs = store.replayOffsetAtStartMs\n\t}\n\t// Payment summary from the first finalise response drives the finished screen\n\t// (complete vs ended-early, reward headline, one-tap payout default).\n\tlet payment: PaymentSummary | null = null\n\tif (store.sessionId) {\n\t\t// First finalise carries durationSeconds + mutedSegments + any un-acked\n\t\t// notes (recovery channel). End-of-test note is sent via a second\n\t\t// finalise call from the thanks screen.\n\t\tconst unackedNotes = store.notes.filter(n => !n.acked).map(n => ({ atMs: n.atMs, text: n.text }))\n\t\tconst result = await finaliseSession(store.options.apiUrl, store.sessionId, durationSeconds, {\n\t\t\tmutedSegments: store.mutedSegments,\n\t\t\tnotes: unackedNotes,\n\t\t\t...replayLinkage,\n\t\t})\n\t\tif (result.ok) {\n\t\t\tpayment = result.payment\n\t\t\t// Mark the un-acked notes we just shipped as acked so the second\n\t\t\t// finalise call doesn't resend them.\n\t\t\tfor (const n of store.notes) {\n\t\t\t\tif (!n.acked) n.acked = true\n\t\t\t}\n\t\t}\n\t\tstore.indicatorState = result.ok ? 'done' : 'error'\n\t} else {\n\t\tstore.indicatorState = 'error'\n\t}\n\trenderIndicatorState(store)\n\n\tif (opts.showThanks && store.indicatorRoot && store.indicatorState === 'done') {\n\t\tshowThanksScreen(store.indicatorRoot, {\n\t\t\tpayment,\n\t\t\tonPayout: async destination => {\n\t\t\t\tif (!store.sessionId) return false\n\t\t\t\treturn postPayout(store.options.apiUrl, store.sessionId, destination, ctx.logger)\n\t\t\t},\n\t\t\tonResume: () => {\n\t\t\t\t// Re-arm an ended-early session: the recording was already stopped +\n\t\t\t\t// finalised, so resuming starts a fresh recording leg under the same\n\t\t\t\t// sessionId. The server is idempotent on a later finalise; the new\n\t\t\t\t// audio chunks continue the same R2 prefix. Reset the finish guard\n\t\t\t\t// and timeline anchor so the next Finish re-evaluates completion.\n\t\t\t\tstore.finishFlowRan = false\n\t\t\t\tstore.indicatorState = 'recording'\n\t\t\t\tstore.startedAt = Date.now()\n\t\t\t\tstore.muted = false\n\t\t\t\tstore.mutedSinceMs = null\n\t\t\t\trenderIndicatorState(store)\n\t\t\t\tvoid startRecording(store, ctx)\n\t\t\t},\n\t\t\tonSubmitNote: async text => {\n\t\t\t\tif (!store.sessionId) return\n\t\t\t\tstore.endNote = text\n\t\t\t\t// Second finalise only carries the late-binding fields. mutedSegments\n\t\t\t\t// already landed on call 1, server stores idempotently, no need to\n\t\t\t\t// resend. Include any notes that arrived (or failed to ack) between\n\t\t\t\t// the two calls.\n\t\t\t\tconst stillUnacked = store.notes.filter(n => !n.acked).map(n => ({ atMs: n.atMs, text: n.text }))\n\t\t\t\tconst result = await finaliseSession(store.options.apiUrl, store.sessionId, durationSeconds, {\n\t\t\t\t\tendNote: text,\n\t\t\t\t\tnotes: stillUnacked,\n\t\t\t\t\t...replayLinkage,\n\t\t\t\t})\n\t\t\t\tif (!result.ok) throw new Error('finalise failed')\n\t\t\t\tfor (const n of store.notes) {\n\t\t\t\t\tif (!n.acked) n.acked = true\n\t\t\t\t}\n\t\t\t},\n\t\t\tonSkip: () => { /* nothing to send */ },\n\t\t})\n\t}\n}\n\nexport function userTest(options: UserTestOptions = {}): UseroPlugin {\n\tconst merged: Required<UserTestOptions> = {\n\t\tqueryParam: options.queryParam ?? DEFAULT_OPTIONS.queryParam,\n\t\tchunkSeconds: options.chunkSeconds ?? DEFAULT_OPTIONS.chunkSeconds,\n\t\tapiUrl: options.apiUrl ?? DEFAULT_OPTIONS.apiUrl,\n\t\ttesterName: options.testerName ?? DEFAULT_OPTIONS.testerName,\n\t\thideIndicator: options.hideIndicator ?? DEFAULT_OPTIONS.hideIndicator,\n\t}\n\n\treturn {\n\t\tname: 'user-test',\n\t\tonInit(ctx) {\n\t\t\tif (typeof window === 'undefined' || typeof document === 'undefined') return\n\t\t\tconst slug = getTestSlug(merged.queryParam)\n\t\t\tif (!slug) return\n\n\t\t\tconst apiUrl = merged.apiUrl || ctx.baseUrl || DEFAULT_API_URL\n\t\t\tconst store: RecorderStore = {\n\t\t\t\tcancelled: false,\n\t\t\t\tslug,\n\t\t\t\tsessionId: null,\n\t\t\t\tclientId: null,\n\t\t\t\trecorder: null,\n\t\t\t\tstream: null,\n\t\t\t\tchunkIndex: 0,\n\t\t\t\tuploadQueue: Promise.resolve(),\n\t\t\t\tpendingUploads: 0,\n\t\t\t\tstartedAt: Date.now(),\n\t\t\t\tindicator: null,\n\t\t\t\tindicatorRoot: null,\n\t\t\t\tindicatorState: 'recording',\n\t\t\t\tpageHideHandler: null,\n\t\t\t\tvisibilityHandler: null,\n\t\t\t\toptions: { ...merged, apiUrl },\n\t\t\t\ttasks: [],\n\t\t\t\ttasksPanelOpen: readTasksPanelOpen(),\n\t\t\t\toutsidePointerHandler: null,\n\t\t\t\tkeydownHandler: null,\n\t\t\t\thasMicPermission: false,\n\t\t\t\tmicAcquiring: true,\n\t\t\t\tmicFailReason: null,\n\t\t\t\tmuted: false,\n\t\t\t\tmutedSinceMs: null,\n\t\t\t\tmutedSegments: [],\n\t\t\t\tmicSilent: false,\n\t\t\t\tsilenceMonitor: null,\n\t\t\t\tmuteToastShown: false,\n\t\t\t\tmuteToastTimers: [],\n\t\t\t\tnotes: [],\n\t\t\t\tnotesPopoverOpen: false,\n\t\t\t\tnotePopoverAtMs: null,\n\t\t\t\tendNote: '',\n\t\t\t\tfinishFlowRan: false,\n\t\t\t\treplayOffsetAtStartMs: null,\n\t\t\t}\n\t\t\tctx.setStore(store)\n\n\t\t\tconst onFinish = (): void => {\n\t\t\t\tvoid finishFlow(store, ctx, { showThanks: true })\n\t\t\t}\n\n\t\t\tconst setPanelOpen = (open: boolean): void => {\n\t\t\t\tif (store.tasksPanelOpen === open) return\n\t\t\t\tstore.tasksPanelOpen = open\n\t\t\t\twriteTasksPanelOpen(open)\n\t\t\t\trenderTasksPanel(store)\n\t\t\t}\n\n\t\t\tconst onToggleTasks = (): void => setPanelOpen(!store.tasksPanelOpen)\n\n\t\t\tconst onToggleMute = (): void => {\n\t\t\t\tif (!store.hasMicPermission) {\n\t\t\t\t\t// In the failed terminal state the chip is a retry button:\n\t\t\t\t\t// re-attempt mic acquisition. Ignore taps while still\n\t\t\t\t\t// acquiring (connecting state is unfocusable anyway).\n\t\t\t\t\tif (!store.micAcquiring && store.indicatorState !== 'finishing' && store.indicatorState !== 'done' && store.indicatorState !== 'error') {\n\t\t\t\t\t\tvoid startRecording(store, ctx)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Silent-mic warning is also a retry affordance: the participant\n\t\t\t\t// has likely switched their input device, so re-acquire the mic\n\t\t\t\t// rather than toggling mute. startRecording tears down the old\n\t\t\t\t// stream + monitor and re-runs detection on the fresh track.\n\t\t\t\tif (store.micSilent && !store.micAcquiring && store.indicatorState !== 'finishing' && store.indicatorState !== 'done' && store.indicatorState !== 'error') {\n\t\t\t\t\tvoid startRecording(store, ctx)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst ok = toggleMute(store)\n\t\t\t\tif (!ok) return\n\t\t\t\tif (store.muted) {\n\t\t\t\t\t// Muting is intentional silence: drop any active silent-mic\n\t\t\t\t\t// warning so the two states don't fight. The monitor's\n\t\t\t\t\t// onChange suppresses re-raising it while muted.\n\t\t\t\t\tstore.micSilent = false\n\t\t\t\t\tshowMuteToast(store)\n\t\t\t\t}\n\t\t\t\trenderIndicatorState(store)\n\t\t\t}\n\n\t\t\tconst closeNote = (): void => closeNotePopover(store)\n\t\t\tconst onOpenNote = (): void => {\n\t\t\t\tif (store.notesPopoverOpen) { closeNote(); return }\n\t\t\t\topenNotePopover(\n\t\t\t\t\tstore,\n\t\t\t\t\ttext => {\n\t\t\t\t\t\tconst atMs = store.notePopoverAtMs ?? Math.max(0, Date.now() - store.startedAt)\n\t\t\t\t\t\tconst note: InFlightNote = { atMs, text, acked: false }\n\t\t\t\t\t\tstore.notes.push(note)\n\t\t\t\t\t\tcloseNote()\n\t\t\t\t\t\trenderNotesCount(store)\n\t\t\t\t\t\t// UI never blocks on the POST. On success we mark the note\n\t\t\t\t\t\t// acked; on failure (after one retry) it stays unacked and\n\t\t\t\t\t\t// gets included in the finalise notes batch as a recovery\n\t\t\t\t\t\t// channel. Server dedupes by (sessionId, atMs, text).\n\t\t\t\t\t\tif (store.sessionId) {\n\t\t\t\t\t\t\tconst sessionId = store.sessionId\n\t\t\t\t\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\t\t\t\t\tconst result = await postNoteWithRetry(store.options.apiUrl, sessionId, atMs, text, ctx.logger)\n\t\t\t\t\t\t\t\tif (result.ok) {\n\t\t\t\t\t\t\t\t\tnote.acked = true\n\t\t\t\t\t\t\t\t\tif (result.id) note.serverId = result.id\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t() => closeNote(),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif (!merged.hideIndicator) {\n\t\t\t\tconst host = document.createElement('div')\n\t\t\t\thost.setAttribute('data-usero-user-test', 'true')\n\t\t\t\tdocument.body.appendChild(host)\n\t\t\t\tstore.indicator = host\n\t\t\t\tstore.indicatorRoot = buildIndicator(host, store, {\n\t\t\t\t\tonFinish,\n\t\t\t\t\tonToggleTasks,\n\t\t\t\t\tonToggleMute,\n\t\t\t\t\tonOpenNote,\n\t\t\t\t})\n\t\t\t\trenderIndicatorState(store)\n\t\t\t\trenderNotesCount(store)\n\t\t\t}\n\n\t\t\t// Outside-click + Escape close the tasks panel. Listen on document\n\t\t\t// (composedPath checks shadow ancestry so taps on the panel/pill\n\t\t\t// itself don't dismiss).\n\t\t\tconst outsidePointer = (event: PointerEvent): void => {\n\t\t\t\tconst host = store.indicator\n\t\t\t\tif (!host) return\n\t\t\t\tconst path = event.composedPath()\n\t\t\t\tif (path.includes(host)) return\n\t\t\t\tif (store.tasksPanelOpen) setPanelOpen(false)\n\t\t\t\tif (store.notesPopoverOpen) closeNote()\n\t\t\t}\n\t\t\tconst onKeydown = (event: KeyboardEvent): void => {\n\t\t\t\tif (event.key !== 'Escape') return\n\t\t\t\tif (store.tasksPanelOpen) setPanelOpen(false)\n\t\t\t\tif (store.notesPopoverOpen) closeNote()\n\t\t\t}\n\t\t\tstore.outsidePointerHandler = outsidePointer\n\t\t\tstore.keydownHandler = onKeydown\n\t\t\tdocument.addEventListener('pointerdown', outsidePointer, true)\n\t\t\tdocument.addEventListener('keydown', onKeydown)\n\n\t\t\tconst pageHide = (): void => {\n\t\t\t\t// Best-effort flush + finalise. We don't await here; the browser\n\t\t\t\t// is shutting the page down. `keepalive: true` on finalise lets\n\t\t\t\t// the request race the unload.\n\t\t\t\tvoid finishFlow(store, ctx, { showThanks: false })\n\t\t\t}\n\t\t\tstore.pageHideHandler = pageHide\n\t\t\twindow.addEventListener('pagehide', pageHide)\n\n\t\t\t// visibilitychange -> hidden is the reliable mobile unload backstop:\n\t\t\t// iOS frequently kills a backgrounded tab WITHOUT ever firing\n\t\t\t// pagehide, leaving the session stuck un-finalised. visibilitychange\n\t\t\t// fires far more consistently when the tab is backgrounded. It runs\n\t\t\t// the SAME finishFlow path as pagehide; finishFlow is idempotent (the\n\t\t\t// `finishFlowRan` + indicatorState short-circuit at its top), so the\n\t\t\t// manual Finish click, pagehide, and this handler can fire in any\n\t\t\t// order or concurrently and only the first does the work. Like\n\t\t\t// pagehide we don't await; finalise rides on `keepalive: true`.\n\t\t\tconst onVisibilityChange = (): void => {\n\t\t\t\tif (document.visibilityState !== 'hidden') return\n\t\t\t\tvoid finishFlow(store, ctx, { showThanks: false })\n\t\t\t}\n\t\t\tstore.visibilityHandler = onVisibilityChange\n\t\t\tdocument.addEventListener('visibilitychange', onVisibilityChange)\n\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\t// If the entry screen created the session and passed `uts`, ADOPT\n\t\t\t\t// it (the participant's email is already attached server-side).\n\t\t\t\t// Otherwise fall back to creating one (open tests / old links).\n\t\t\t\tconst adoptId = getAdoptSessionId()\n\t\t\t\tconst created = adoptId\n\t\t\t\t\t? await adoptSession(apiUrl, adoptId)\n\t\t\t\t\t: await createSession(apiUrl, slug, readTesterName(merged.testerName))\n\t\t\t\tif (store.cancelled) return\n\t\t\t\tif (!created) {\n\t\t\t\t\tctx.logger.error(adoptId ? 'failed to adopt user-test session' : 'failed to create user-test session')\n\t\t\t\t\tstore.indicatorState = 'error'\n\t\t\t\t\trenderIndicatorState(store)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tstore.sessionId = created.sessionId\n\t\t\t\tstore.clientId = created.clientId\n\t\t\t\t// Capture the replay offset HERE at session start (not at\n\t\t\t\t// finalise) so it reflects when the test began relative to the\n\t\t\t\t// recording. The replay plugin publishes its start epoch into\n\t\t\t\t// the core; we read it via the context. If replay is not active\n\t\t\t\t// (plugin not loaded, sampled out, or an older host without the\n\t\t\t\t// accessor) we leave it null and the finalise body omits\n\t\t\t\t// replayOffsetMs. Anchored to store.startedAt (test start),\n\t\t\t\t// clamped >= 0 in case the test starts a hair before the replay\n\t\t\t\t// epoch is published.\n\t\t\t\tconst replayStartMs = ctx.getReplayStartMs ? ctx.getReplayStartMs() : null\n\t\t\t\tstore.replayOffsetAtStartMs =\n\t\t\t\t\treplayStartMs === null ? null : Math.max(0, store.startedAt - replayStartMs)\n\t\t\t\tstore.tasks = created.tasks\n\t\t\t\tif (store.tasks.length > 0 && store.indicatorRoot && !merged.hideIndicator) {\n\t\t\t\t\tconst bar = store.indicatorRoot.querySelector('.bar')\n\t\t\t\t\tconst finishBtn = bar?.querySelector('.finish-btn')\n\t\t\t\t\tif (bar instanceof HTMLElement && finishBtn instanceof HTMLElement && !bar.querySelector('.tasks-btn')) {\n\t\t\t\t\t\tinstallTasksToggle(bar, finishBtn, store, onToggleTasks)\n\t\t\t\t\t}\n\t\t\t\t\trenderTasksPanel(store)\n\t\t\t\t}\n\t\t\t\tawait startRecording(store, ctx)\n\t\t\t\trenderIndicatorState(store)\n\t\t\t})()\n\t\t},\n\t\tonDestroy(ctx) {\n\t\t\tconst store = ctx.getStore<RecorderStore>()\n\t\t\tif (!store) return\n\t\t\tstore.cancelled = true\n\t\t\tif (store.pageHideHandler) {\n\t\t\t\twindow.removeEventListener('pagehide', store.pageHideHandler)\n\t\t\t\tstore.pageHideHandler = null\n\t\t\t}\n\t\t\tif (store.visibilityHandler) {\n\t\t\t\tdocument.removeEventListener('visibilitychange', store.visibilityHandler)\n\t\t\t\tstore.visibilityHandler = null\n\t\t\t}\n\t\t\tstopRecording(store)\n\t\t\tif (store.outsidePointerHandler) {\n\t\t\t\tdocument.removeEventListener('pointerdown', store.outsidePointerHandler, true)\n\t\t\t\tstore.outsidePointerHandler = null\n\t\t\t}\n\t\t\tif (store.keydownHandler) {\n\t\t\t\tdocument.removeEventListener('keydown', store.keydownHandler)\n\t\t\t\tstore.keydownHandler = null\n\t\t\t}\n\t\t\tfor (const id of store.muteToastTimers) {\n\t\t\t\ttry { window.clearTimeout(id) } catch { /* ignore */ }\n\t\t\t}\n\t\t\tstore.muteToastTimers = []\n\t\t\tif (store.indicator && store.indicator.parentNode) {\n\t\t\t\tstore.indicator.parentNode.removeChild(store.indicator)\n\t\t\t}\n\t\t\tstore.indicator = null\n\t\t\tstore.indicatorRoot = null\n\t\t},\n\t}\n}\n\n// Internal helpers exposed for tests only. Not part of the public API.\nexport const __test__ = {\n\tgetTestSlug,\n\tpickMimeType,\n\tisMediaRecorderSupported,\n\tmicChipState,\n\tisStreamSilent,\n\trmsDbFromSamples,\n\tSILENCE_RMS_DB_THRESHOLD,\n\tSILENCE_FLOOR_DB,\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts","../../src/identity.ts","../../src/plugins/user-test/shared.ts","../../src/plugins/user-test/session.ts","../../src/plugins/user-test/ui.ts","../../src/plugins/user-test/recorder.ts","../../src/plugins/user-test/lifecycle.ts","../../src/plugins/user-test.ts"],"names":[],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","file":"user-test.cjs","sourcesContent":["// Shared types used by both the vanilla and React entry points.\n// Keep this file framework-free so the vanilla bundle never pulls react.\n\nexport type FeedbackRating = 1 | 2 | 3 | 4\n\nexport interface FeedbackMetadata {\n\tpageUrl: string\n\tpageTitle: string\n\treferrer?: string\n\ttimestamp: number\n}\n\nexport interface ScreenshotData {\n\tfileName: string\n\turl: string\n\tfileSize: number\n\twidth?: number\n\theight?: number\n\tmimeType: string\n}\n\nexport interface FeedbackSubmission {\n\tclientId: string\n\trating?: FeedbackRating\n\tcomment?: string\n\tuserEmail?: string\n\tpageUrl: string\n\tpageTitle: string\n\treferrer?: string\n\tenvironment?: string\n\tscreenshots?: ScreenshotData[]\n\tmetadata?: Record<string, unknown>\n\t// Legacy gzipped + base64-encoded rrweb event stream. The pre-chunked\n\t// session-replay plugin attached this on submit. The chunked-upload\n\t// plugin (>= 0.4.0) does NOT set this — it ships events out-of-band\n\t// via the chunk endpoints and points at the resulting session replay\n\t// via `sessionReplayId` + `replayOffsetMs`. Kept on the wire one\n\t// release for backward compat with old SaaS deployments that only\n\t// know how to ingest the legacy field.\n\treplayEvents?: string\n\t// Pointer to a SessionReplay row created by the session-replay\n\t// plugin's chunked-upload pipeline, plus the offset within that\n\t// recording at submit time so the dashboard can deep-link.\n\tsessionReplayId?: string\n\treplayOffsetMs?: number\n}\n\nexport interface FeedbackData {\n\trating?: FeedbackRating\n\tcomment?: string\n\tuserEmail?: string\n\tscreenshots?: ScreenshotData[]\n\tmetadata: FeedbackMetadata\n}\n\n// Customer-supplied identity for the session-replay / identify pipeline.\n// Use the declarative form: pass `user` on the React widget (the SDK\n// diffs and auto-fires identify), or supply `getUser` on the vanilla\n// init (the SDK polls it at session start). An imperative `identify()`\n// call exists as an escape hatch only and is intentionally not\n// documented as the headline API.\nexport type UseroUserTraitValue = string | number | boolean | null\nexport type UseroUserTraits = Record<string, UseroUserTraitValue>\nexport interface UseroUser {\n\tid: string\n\temail?: string\n\tdisplayName?: string\n\ttraits?: UseroUserTraits\n}\n\nexport type WidgetPosition = 'right' | 'left'\n\nexport interface WidgetTheme {\n\tprimary: string\n\tbackground: string\n\ttext: string\n\tborder: string\n\tshadow: string\n}\n\n// Forward-declared to avoid a circular import. The plugin module imports\n// FeedbackSubmission from this file, so we can't import UseroPlugin back\n// up here without a cycle. Keeping the prop typed as an opaque object with\n// the minimal shape the widget actually inspects works fine for consumers.\nexport interface FeedbackWidgetProps {\n\tclientId: string\n\tposition?: WidgetPosition\n\ttheme?: Partial<WidgetTheme>\n\ttitle?: string\n\tplaceholder?: string\n\tshowEmailOption?: boolean\n\tshowScreenshotOption?: boolean\n\tenvironment?: string\n\tbaseUrl?: string\n\tmetadata?: Record<string, unknown>\n\tplugins?: ReadonlyArray<import('./plugin').UseroPlugin>\n\t// Declarative identity. React: pass the current user (or null on\n\t// logout) and the SDK auto-fires identify when the resolved id\n\t// transitions. Vanilla: pass a getter so the SDK can resolve user at\n\t// session start / chunk boundaries. Pass at most one. The SDK never\n\t// invokes both.\n\tuser?: UseroUser | null\n\tgetUser?: () => UseroUser | null | undefined\n\tonSubmit?: (feedback: FeedbackData) => void\n\tonError?: (error: Error) => void\n\tonOpen?: () => void\n\tonClose?: () => void\n}\n\nexport interface SubmissionResponse {\n\tsuccess: boolean\n\terror?: string\n\tid?: string\n\tmessage?: string\n\tdata?: unknown\n}\n\nexport const EMOJI_MAP: Record<FeedbackRating, string> = {\n\t1: '😞',\n\t2: '😐',\n\t3: '😊',\n\t4: '🤩',\n}\n\nexport const RATING_LABELS: Record<FeedbackRating, string> = {\n\t1: 'Needs work',\n\t2: \"It's okay\",\n\t3: 'Pretty good',\n\t4: 'Amazing!',\n}\n\nexport const EMOJI_BACKGROUNDS: Record<FeedbackRating, string> = {\n\t1: 'linear-gradient(135deg,#ff6b6b14,#ff6b6b1f)',\n\t2: 'linear-gradient(135deg,#9ca3af0f,#9ca3af1a)',\n\t3: 'linear-gradient(135deg,#3b82f614,#3b82f61f)',\n\t4: 'linear-gradient(135deg,#f59e0b14,#f59e0b1f)',\n}\n\nexport const DEFAULT_API_URL = 'https://usero.io'\n\nexport const DEFAULT_THEME: WidgetTheme = {\n\tprimary: '#2563eb',\n\tbackground: '#ffffff',\n\ttext: '#374151',\n\tborder: '#e5e7eb',\n\tshadow:\n\t\t'0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',\n}\n\nexport const DARK_THEME: WidgetTheme = {\n\tprimary: '#2563eb',\n\tbackground: '#1f2937',\n\ttext: '#f9fafb',\n\tborder: '#374151',\n\tshadow:\n\t\t'0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2)',\n}\n\nexport function mergeTheme(customTheme: Partial<WidgetTheme> = {}): WidgetTheme {\n\treturn { ...DEFAULT_THEME, ...customTheme }\n}\n","// Identity layer for the Usero SDK.\n//\n// Two responsibilities:\n// 1. Mint and persist a stable per-browser `anonymousId` in localStorage\n// so cross-tab + cross-day replays from the same browser stitch\n// together server-side. Falls back to an in-memory id if storage is\n// blocked (sandboxed iframes, Safari Lockdown, full quota). Replay\n// still works in that case, you just lose stitching.\n// 2. Auto-fire POST /api/identify when the resolved user transitions\n// (null -> id, id -> id'). Deduped by an in-memory fingerprint so\n// re-renders with the same user are no-ops on the network.\n//\n// All storage access is wrapped in try/catch and gated behind a one-shot\n// init read. The hot path (replay chunk flush) never touches localStorage.\n\nconst ANON_STORAGE_KEY = 'usero:anonymous-id'\n// Reuse the EXACT key the session-replay plugin has always written\n// (`usero:session-replay:sdk-session-id`) so we adopt per-tab session ids\n// already living in customers' browsers instead of minting a fresh one.\n// Changing this would split a tab's existing replay from its user-test\n// linkage, so it must stay byte-for-byte identical to the replay plugin's\n// historical key.\nconst SDK_SESSION_STORAGE_KEY = 'usero:session-replay:sdk-session-id'\n\nlet cachedAnonymousId: string | null = null\nlet cachedSdkSessionId: string | null = null\n// Last resolved external user id, set by the identify pipeline so any\n// plugin can read the current identity without re-resolving the host's\n// `user` prop. Cleared on logout (anonymousId rotation).\nlet currentUserId: string | null = null\n// Wall-clock epoch (ms) when the session-replay recording started, as\n// published by the replay plugin. Lets other plugins (user-test) compute\n// an offset into the recording without importing the replay module. Null\n// until replay actually starts (or if replay is not loaded at all).\nlet replayStartMs: number | null = null\n// Fingerprint of the last identify we POSTed. Same SDK instance + same\n// resolved user + same traits = no-op. Cleared on logout (anonymousId\n// rotation).\nlet lastIdentifyFingerprint: string | null = null\n\nexport type UserTraitValue = string | number | boolean | null\nexport type UserTraits = Record<string, UserTraitValue>\n\nexport interface UseroUser {\n\tid: string\n\temail?: string\n\tdisplayName?: string\n\ttraits?: UserTraits\n}\n\nfunction generateRandomId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID()\n\t}\n\tconst bytes = new Uint8Array(16)\n\tif (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n\t\tcrypto.getRandomValues(bytes)\n\t} else {\n\t\tfor (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256)\n\t}\n\tlet out = ''\n\tfor (const b of bytes) out += b.toString(16).padStart(2, '0')\n\treturn out\n}\n\nfunction safeReadLocalStorage(key: string): string | null {\n\tif (typeof window === 'undefined') return null\n\ttry {\n\t\treturn window.localStorage?.getItem(key) ?? null\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction safeWriteLocalStorage(key: string, value: string): void {\n\tif (typeof window === 'undefined') return\n\ttry {\n\t\twindow.localStorage?.setItem(key, value)\n\t} catch {\n\t\t// Sandboxed iframe / Safari Lockdown / quota. Fall back to memory.\n\t}\n}\n\nfunction safeReadSessionStorage(key: string): string | null {\n\tif (typeof window === 'undefined') return null\n\ttry {\n\t\treturn window.sessionStorage?.getItem(key) ?? null\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction safeWriteSessionStorage(key: string, value: string): void {\n\tif (typeof window === 'undefined') return\n\ttry {\n\t\twindow.sessionStorage?.setItem(key, value)\n\t} catch {\n\t\t// Sandboxed iframe / Safari Lockdown / quota. Fall back to memory.\n\t}\n}\n\n/**\n * Returns the stable per-browser anonymousId. Reads localStorage at most\n * once per SDK instance. Subsequent calls hit the in-memory cache, so\n * even hot paths (per-event in replay) are safe to call this.\n */\nexport function getOrMintAnonymousId(): string {\n\tif (cachedAnonymousId) return cachedAnonymousId\n\tconst existing = safeReadLocalStorage(ANON_STORAGE_KEY)\n\t// Sanity filter, not strict validation. We accept anything that looks\n\t// plausibly like an id (>=8 alphanumeric-or-hyphen) so older SDK\n\t// versions that wrote a slightly different shape still stitch. Fresh\n\t// mint is cheap, so we only reject obvious garbage; tightening this\n\t// would force rotation in customer browsers and split otherwise-good\n\t// sibling-session attribution.\n\tif (existing && /^[a-z0-9-]{8,}$/i.test(existing)) {\n\t\tcachedAnonymousId = existing\n\t\treturn existing\n\t}\n\tconst id = generateRandomId()\n\tsafeWriteLocalStorage(ANON_STORAGE_KEY, id)\n\tcachedAnonymousId = id\n\treturn id\n}\n\n/**\n * Rotate the anonymousId. Called on logout (user transitions from a\n * known id to null) so the next anonymous trail does not get auto-merged\n * into the previous person on the next identify().\n */\nexport function rotateAnonymousId(): string {\n\tconst id = generateRandomId()\n\tcachedAnonymousId = id\n\tsafeWriteLocalStorage(ANON_STORAGE_KEY, id)\n\tlastIdentifyFingerprint = null\n\tcurrentUserId = null\n\treturn id\n}\n\n/**\n * Loose sanity filter for a persisted/incoming sdkSessionId: >=8 chars of\n * alphanumerics or hyphens. NOT strict validation. We accept anything that\n * looks plausibly like an id so older SDK builds (and cross-plugin re-seats)\n * still stitch; a fresh mint is cheap, so we only reject obvious garbage.\n * Tightening this would force rotation in customer browsers and split\n * otherwise-good sibling-session attribution. Single source of truth so the\n * mint / read / re-seat / resume-persist paths can never drift apart.\n */\nexport function isValidSdkSessionId(id: string): boolean {\n\treturn /^[a-z0-9-]{8,}$/i.test(id)\n}\n\n/**\n * Returns the stable per-tab sdkSessionId. Core-owned so every plugin\n * (replay, user-test, future feedback linkage) reads the SAME id for a\n * given tab. Reads sessionStorage at most once per SDK instance; later\n * calls hit the in-memory cache.\n *\n * Backward compat: reuses the `usero:sdk-session-id` key the replay\n * plugin has always written, and the same loose sanity filter, so an\n * id minted by an older replay-only SDK build is adopted as-is rather\n * than rotated (which would split a tab's replay from its user-test).\n */\nexport function getOrMintSdkSessionId(): string {\n\tif (cachedSdkSessionId) return cachedSdkSessionId\n\tconst existing = safeReadSessionStorage(SDK_SESSION_STORAGE_KEY)\n\tif (existing && isValidSdkSessionId(existing)) {\n\t\tcachedSdkSessionId = existing\n\t\treturn existing\n\t}\n\tconst id = generateRandomId()\n\tsafeWriteSessionStorage(SDK_SESSION_STORAGE_KEY, id)\n\tcachedSdkSessionId = id\n\treturn id\n}\n\n/**\n * Re-seat the per-tab sdkSessionId to a specific value. Used ONLY by the\n * user-test resume path: a cross-origin hard navigation (e.g. an OAuth round\n * trip) wipes sessionStorage, so the post-nav document would mint a NEW\n * sdkSessionId and the post-nav SessionReplay row would no longer share an id\n * with the audio session, breaking the (clientId, sdkSessionId) finalise join.\n *\n * The user-test plugin durably persists the original id in its localStorage\n * resume state (which survives the round trip) and calls this synchronously at\n * the very top of its onInit, BEFORE the session-replay plugin reads the id,\n * so the resumed replay reuses the SAME id and the server stitches both replay\n * rows under it.\n *\n * No-ops on a malformed id (so a corrupted resume entry can never poison the\n * tab's id) and on an id identical to the current one. Writes sessionStorage\n * AND the in-memory cache so a replay onInit that already cached a fresh mint\n * is corrected (the cache is the read-through path).\n */\nexport function reseatSdkSessionId(id: string): void {\n\tif (!isValidSdkSessionId(id)) return\n\tif (cachedSdkSessionId === id) return\n\tcachedSdkSessionId = id\n\tsafeWriteSessionStorage(SDK_SESSION_STORAGE_KEY, id)\n}\n\n/**\n * The current resolved external user id, or null if no identify has\n * succeeded this session (or after logout). Set by the identify\n * pipeline; read by plugins that want the current identity without\n * re-resolving the host's `user` prop.\n */\nexport function getCurrentUserId(): string | null {\n\treturn currentUserId\n}\n\n/**\n * Published by the session-replay plugin when its recording starts.\n * Other plugins read it via `getReplayStartMs()` to compute an offset\n * into the recording. No-op duplicate publishes keep the first value so\n * the offset stays anchored to the true recording start.\n */\nexport function publishReplayStartMs(epochMs: number): void {\n\tif (replayStartMs === null) replayStartMs = epochMs\n}\n\n/**\n * The wall-clock epoch (ms) when session-replay started, or null if\n * replay never started this session (plugin not loaded, or sampled out).\n * Consumers degrade gracefully when null: ship the sdkSessionId linkage\n * key anyway and omit the offset.\n */\nexport function getReplayStartMs(): number | null {\n\treturn replayStartMs\n}\n\nfunction fingerprintUser(anonymousId: string, user: UseroUser): string {\n\t// Stable across re-renders: keys sorted, traits canonicalised. Cheap\n\t// enough on the hot path (only runs when the SDK thinks the user might\n\t// have changed, never per-event).\n\tconst traits = user.traits ?? {}\n\tconst keys = Object.keys(traits).sort()\n\tconst canonical: Array<[string, UserTraitValue]> = keys.map(k => [k, traits[k] ?? null])\n\treturn JSON.stringify([anonymousId, user.id, user.email ?? null, user.displayName ?? null, canonical])\n}\n\nexport interface IdentifyTransport {\n\tapiUrl: string\n\tclientId: string\n}\n\n/**\n * POST to /api/identify if the (anonymousId, user) fingerprint differs\n * from the last call. Returns true if a network request actually fired.\n * Never throws; failures are best-effort and the caller (the widget /\n * provider) should not treat them as errors.\n *\n * Tab-unload safety: if the page is hidden when this fires (visibility\n * 'hidden' or a pagehide handler), we route the payload through\n * `navigator.sendBeacon` so the request survives unload. Otherwise we\n * use a normal fetch and only cache the fingerprint when the server\n * confirms `accepted: true`. A 200 `{ accepted: false }` (e.g.\n * `unknown_client` for a clientId that becomes valid mid-session) is\n * treated as retryable so the next call re-fires.\n */\nexport async function identifyIfChanged(transport: IdentifyTransport, user: UseroUser): Promise<boolean> {\n\tconst anonymousId = getOrMintAnonymousId()\n\t// Make the resolved id readable by other plugins immediately, even\n\t// before the network round-trip resolves. This is the identity the\n\t// host just told us about; the POST below is best-effort persistence.\n\tcurrentUserId = user.id\n\tconst fp = fingerprintUser(anonymousId, user)\n\tif (fp === lastIdentifyFingerprint) return false\n\n\tconst url = `${transport.apiUrl.replace(/\\/$/, '')}/api/identify`\n\t// Body must stay under the browser's keepalive / sendBeacon cap\n\t// (~64KB across most engines) when this fires on pagehide. That\n\t// transitively caps trait payload size; in practice traits should be\n\t// small typed scalars, not blobs.\n\tconst body = JSON.stringify({\n\t\tclientId: transport.clientId,\n\t\tanonymousId,\n\t\texternalUserId: user.id,\n\t\temail: user.email,\n\t\tdisplayName: user.displayName,\n\t\ttraits: user.traits,\n\t})\n\n\t// If the document is hidden (pagehide / tab close in flight), best-effort\n\t// hand off to sendBeacon. We don't get a response back, so we optimistically\n\t// cache the fingerprint to avoid re-firing on the next page; the server is\n\t// idempotent if the page reload re-runs identify with the same payload.\n\tif (\n\t\ttypeof document !== 'undefined' &&\n\t\tdocument.visibilityState === 'hidden' &&\n\t\ttypeof navigator !== 'undefined' &&\n\t\ttypeof navigator.sendBeacon === 'function'\n\t) {\n\t\ttry {\n\t\t\tconst blob = new Blob([body], { type: 'application/json' })\n\t\t\t// sendBeacon returns false when the user agent refuses to queue\n\t\t\t// the request (size cap, or historically Safari rejecting\n\t\t\t// non-CORS-simple content types). Modern Safari accepts\n\t\t\t// application/json, but we keep a keepalive-fetch fallback so an\n\t\t\t// older WebKit that rejects the beacon still ships the identify.\n\t\t\tif (navigator.sendBeacon(url, blob)) {\n\t\t\t\tlastIdentifyFingerprint = fp\n\t\t\t\treturn true\n\t\t\t}\n\t\t} catch {\n\t\t\t// fall through to keepalive fetch below\n\t\t}\n\t}\n\n\ttry {\n\t\tconst res = await fetch(url, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody,\n\t\t\t// keepalive lets the request survive a tab-close mid-flight on\n\t\t\t// browsers that support it; sendBeacon above is the primary path.\n\t\t\tkeepalive: true,\n\t\t})\n\t\tif (!res.ok) return true\n\t\t// Parse the response: a 200 with `accepted: false` (e.g. unknown\n\t\t// client) is retryable. Only cache the fingerprint when the server\n\t\t// confirmed it actually stored the identity.\n\t\ttry {\n\t\t\tconst json = (await res.json()) as { accepted?: unknown }\n\t\t\tif (json && json.accepted === true) lastIdentifyFingerprint = fp\n\t\t} catch {\n\t\t\t// Server returned 2xx but unparseable body: don't cache, let the\n\t\t\t// next call retry.\n\t\t}\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Clear identify state and rotate anonymousId. Called when the resolved\n * user transitions from a known id to null (logout). The next anonymous\n * trail will get a fresh anonymousId so it does not merge into the\n * previous person.\n */\nexport function handleLogout(): void {\n\trotateAnonymousId()\n}\n\n// Test hooks (not exported from the package public surface).\nexport const __test__ = {\n\tANON_STORAGE_KEY,\n\tSDK_SESSION_STORAGE_KEY,\n\treseatSdkSessionId,\n\tgetOrMintSdkSessionId,\n\tresetIdentityState: (): void => {\n\t\tcachedAnonymousId = null\n\t\tcachedSdkSessionId = null\n\t\tcurrentUserId = null\n\t\treplayStartMs = null\n\t\tlastIdentifyFingerprint = null\n\t},\n}\n","// Shared types, constants, and inline icon assets for the user-test plugin.\n// Pure data + type declarations only: no DOM, no network, no logic. Every other\n// module in this directory imports from here so the literals live in one place.\n\nimport type { PluginContext } from '../../plugin'\nimport { DEFAULT_API_URL } from '../../types'\n\nexport interface UserTestOptions {\n\t// URL query param the welcome page appends to redirect testers back.\n\t// Default `usero_test`. Match SaaS side if you ever change it.\n\tqueryParam?: string\n\t// Audio chunk length in seconds. Smaller = more partial-data resilience\n\t// but more requests. Default 10.\n\tchunkSeconds?: number\n\t// API origin. Override for self-hosted or local dev. Defaults to the\n\t// shared SDK constant (https://usero.io).\n\tapiUrl?: string\n\t// Override the tester-name shown on the SaaS side. Normally the welcome\n\t// page collects this and stores it in localStorage; the plugin reads it\n\t// from there. This option lets a host site bypass that.\n\ttesterName?: string\n\t// Hide the floating indicator. The plugin still records and finalises\n\t// on `pagehide`, but the tester gets no on-page UI. Useful if the host\n\t// page wants to render its own.\n\thideIndicator?: boolean\n}\n\nexport interface UserTestTask {\n\tid: string\n\tprompt: string\n\tsortOrder: number\n}\n\nexport interface MutedSegment {\n\tstartMs: number\n\tendMs: number\n}\n\nexport interface InFlightNote {\n\tatMs: number\n\ttext: string\n\tacked: boolean\n\tserverId?: string\n}\n\nexport interface RecorderStore {\n\tcancelled: boolean\n\tslug: string\n\tsessionId: string | null\n\tclientId: string | null\n\trecorder: MediaRecorder | null\n\tstream: MediaStream | null\n\tchunkIndex: number\n\tuploadQueue: Promise<void>\n\tpendingUploads: number\n\tstartedAt: number\n\tindicator: HTMLElement | null\n\tindicatorRoot: ShadowRoot | null\n\tindicatorState: 'recording' | 'finishing' | 'done' | 'no-audio' | 'error'\n\tpageHideHandler: (() => void) | null\n\tvisibilityHandler: (() => void) | null\n\toptions: Required<UserTestOptions>\n\ttasks: UserTestTask[]\n\ttasksPanelOpen: boolean\n\toutsidePointerHandler: ((event: PointerEvent) => void) | null\n\tkeydownHandler: ((event: KeyboardEvent) => void) | null\n\t// Mic mute\n\thasMicPermission: boolean\n\t// True while getUserMedia is in flight (pending). Distinguishes the\n\t// \"connecting\" chip state (granted users, promise not yet resolved) from\n\t// the genuinely-failed terminal \"none\" state. Init true; cleared on every\n\t// success/failure exit of startRecording.\n\tmicAcquiring: boolean\n\t// Why mic acquisition failed, for actionable terminal copy. 'blocked' =\n\t// permission denied (NotAllowedError), 'not-found' = no device\n\t// (NotFoundError), 'unsupported' = MediaRecorder/getUserMedia missing or\n\t// constructor threw. Null while acquiring or once granted.\n\tmicFailReason: 'blocked' | 'not-found' | 'unsupported' | null\n\tmuted: boolean\n\tmutedSinceMs: number | null\n\tmutedSegments: MutedSegment[]\n\t// Silent-mic guard. micSilent is true while the live input is reading\n\t// digital silence (dead mic, or a virtual audio device delivering nothing).\n\t// Non-blocking: recording continues; the pill just warns. Auto-clears when\n\t// real audio returns. silenceMonitor holds the AnalyserNode teardown.\n\tmicSilent: boolean\n\tsilenceMonitor: { stop(): void } | null\n\tmuteToastShown: boolean\n\tmuteToastTimers: number[]\n\t// Timers for the \"Recording resumed\" confirmation pill (shown once after a\n\t// resume across a hard navigation). Tracked so onDestroy can clear them.\n\tresumeToastTimers: number[]\n\t// In-flight notes\n\tnotes: InFlightNote[]\n\tnotesPopoverOpen: boolean\n\tnotePopoverAtMs: number | null\n\t// End-of-test comment (collected on thanks screen)\n\tendNote: string\n\t// Re-entry guard for finishFlow.\n\tfinishFlowRan: boolean\n\t// Set once the chunk-upload path sees a definitive \"session closed\" signal\n\t// from the server (409 + closeResume: true, raised when finalise has already\n\t// snapshotted the session). Guards against running the terminal close flow\n\t// twice when several queued chunks all get rejected. Once true, enqueueChunk\n\t// drops further chunks rather than stashing them for a retry that can never\n\t// land.\n\tsessionClosed: boolean\n\t// Terminal-close handler, wired in the entry module where the indicatorRoot\n\t// and hideIndicator option are in scope. Invoked once by the chunk-upload\n\t// path when the server reports the session is closed: it stops recording,\n\t// clears the persisted resume state, and shows the \"session ended\" screen.\n\t// Null until the entry module wires it (and when there's nothing to close).\n\tonSessionClosed: (() => void) | null\n\t// True once pauseFlow has run for a hard navigation (page hidden mid-test).\n\t// Set BEFORE stopRecording() so the final trailing `dataavailable` chunk that\n\t// stopRecording triggers persists as 'paused' (keeping pausedAt), not 'active'.\n\t// Without this the trailing enqueueChunk rewrote the entry to 'active' and\n\t// dropped pausedAt, defeating the RESUME_MAX_IDLE_MS idle gate. Reset to false\n\t// on a successful resume (the recorder is live again).\n\tpaused: boolean\n\t// True when this store was rehydrated from persisted localStorage state\n\t// (a resume across a hard navigation) rather than a fresh start. Lets the\n\t// init path skip session creation/adoption and continue the chunk index.\n\tresumed: boolean\n\t// The per-tab sdkSessionId for this session, resolved once at session start\n\t// from the core (ctx.getSdkSessionId). Mirrored into the localStorage resume\n\t// state so a hard nav can re-seat it and keep the replay link intact, and\n\t// sent on every finalise so the (clientId, sdkSessionId) join resolves even\n\t// after resume. Null when replay is not active / the host predates the core\n\t// accessor.\n\tsdkSessionId: string | null\n\t// Offset (ms) into the session-replay recording at the moment THIS\n\t// user-test session started. Captured once at start (not at finalise)\n\t// so it pins the test timeline to the recording timeline. Null when no\n\t// replay is active (plugin not loaded, sampled out, or host predates\n\t// the core accessor); finalise then omits replayOffsetMs.\n\treplayOffsetAtStartMs: number | null\n}\n\nexport const DEFAULT_OPTIONS: Required<Omit<UserTestOptions, 'testerName' | 'apiUrl'>> & {\n\ttesterName: string\n\tapiUrl: string\n} = {\n\tqueryParam: 'usero_test',\n\t// 10s (not 30) so at most ~10s of audio is at risk if the tab is torn\n\t// down before a flush, and so a session shorter than the old 30s window\n\t// still emits at least one chunk (previously its single buffered chunk was\n\t// never flushed and its audio was lost). Tradeoff: ~3x the R2 writes /\n\t// upload requests per session; 10s is an acceptable balance, don't go lower.\n\tchunkSeconds: 10,\n\tapiUrl: DEFAULT_API_URL,\n\ttesterName: '',\n\thideIndicator: false,\n}\n\nexport const TESTER_NAME_STORAGE_KEY = 'usero:user-test:tester-name'\nexport const TASKS_PANEL_OPEN_STORAGE_KEY = 'usero:user-test:tasks-panel-open'\n// Per-origin resume state. A single active user-test session per origin: the\n// participant can only be inside one test at a time, so we don't key by slug.\n// localStorage (not sessionStorage) so the entry survives a hard cross-origin\n// navigation away-and-back (e.g. an OAuth round-trip) within the same tab.\nexport const ACTIVE_SESSION_STORAGE_KEY = 'usero:user-test:active-session'\n// A persisted active session older than this is treated as stale and ignored on\n// init, so a long-abandoned entry can never silently start recording on an\n// unrelated later visit. Matches the spirit of the server-side stale sweep.\nexport const ACTIVE_SESSION_MAX_AGE_MS = 2 * 60 * 60 * 1000 // 2h\n// Max idle time between the page hiding (pauseFlow writes pausedAt) and the\n// participant returning, for a paused session to still be RESUME-eligible. The\n// 2h ACTIVE_SESSION_MAX_AGE_MS cap is anchored to startedAt (test start) and is\n// never refreshed, so without this an unrelated return to the origin within 2h\n// would re-adopt the test and silently re-acquire the mic. 30 min is a sane\n// \"they stepped away and came back\" bound; longer idle => session is abandoned.\nexport const RESUME_MAX_IDLE_MS = 30 * 60 * 1000 // 30m\nexport const IDB_NAME = 'usero-user-test'\nexport const IDB_STORE = 'pending-chunks'\n\n// Persisted resume state. Written when recording starts and updated as the\n// chunk index advances; read on init to resume across a hard navigation.\n// - `status`: 'active' while recording, 'paused' once the page hid mid-test.\n// Both resume the same way; the flag is informational (and lets a future\n// UI distinguish \"came back from a pause\" if it wants to).\n// - `nextChunkIndex`: the chunk index the resumed recorder continues from.\n// The server stitches chunks, and the container remux tolerates the extra\n// WebM header the fresh post-resume recorder emits.\nexport interface ActiveSessionState {\n\tslug: string\n\tsessionId: string\n\tnextChunkIndex: number\n\tstartedAt: number\n\tstatus: 'active' | 'paused'\n\t// The per-tab sdkSessionId at the time recording started. Durably mirrored\n\t// here (localStorage survives a cross-origin hard nav; sessionStorage does\n\t// not) so the post-nav document can re-seat the SAME id and the resumed\n\t// SessionReplay row stays joined to this audio session via the server's\n\t// (clientId, sdkSessionId) match. Optional: absent when replay was not active\n\t// (nothing to keep linked), or when an older SDK wrote the entry.\n\tsdkSessionId?: string\n\t// Wall-clock ms when the page last hid mid-test (pauseFlow). Distinct from\n\t// startedAt (test start, used for duration/anchoring, never refreshed).\n\t// Resume eligibility is gated on time-since-pausedAt (RESUME_MAX_IDLE_MS) so\n\t// returning to the origin hours later for an UNRELATED reason can't silently\n\t// re-adopt the test and re-acquire the mic. Absent until the first pause.\n\tpausedAt?: number\n}\n\nexport interface PendingChunk {\n\tid: string\n\tsessionId: string\n\tapiUrl: string\n\tchunkIndex: number\n\tblob: Blob\n\tcreatedAt: number\n}\n\nexport interface IndicatorCallbacks {\n\tonFinish: () => void\n\tonToggleTasks: () => void\n\tonToggleMute: () => void\n\tonOpenNote: () => void\n}\n\n// Result of adopting (or re-adopting on resume) a server-created session.\n// - 'ok': adopted, carry on recording.\n// - 'closed': the server rejected with 409/410 because the session is already\n// completed/failed (e.g. finalised by the stale sweep). The caller MUST\n// clear resume state and NOT start recording, so we never resurrect a closed\n// session and upload post-finalise chunks.\n// - 'error': not found / network / malformed. Treated as a hard failure (no\n// resume), same as the legacy null return.\nexport type AdoptResult =\n\t| { kind: 'ok'; sessionId: string; clientId: string; tasks: UserTestTask[] }\n\t| { kind: 'closed' }\n\t| { kind: 'error' }\n\n// Outcome of a single chunk upload (after its internal retry loop). The chunk\n// path must distinguish three cases, because each demands different handling:\n// - 'ok': the chunk landed (2xx, or the server's idempotent re-fire success).\n// Normal path, nothing more to do.\n// - 'closed': the server returned a definitive \"session is closed\" signal\n// (409 + closeResume: true, raised once finalise has snapshotted the\n// session). Recording must STOP, resume state must be cleared, and the\n// terminal screen shown. NEVER retry or stash: the chunk can never land.\n// - 'failed': a transient failure (network blip, 5xx, 408/429) that exhausted\n// the retry budget, OR a non-closing 4xx rejection. The session is still\n// considered live, so the chunk is stashed in IndexedDB for a later offline\n// flush. This is the pre-existing \"return false\" behaviour.\nexport type ChunkUploadOutcome = 'ok' | 'closed' | 'failed'\n\nexport interface FinaliseNote {\n\tatMs: number\n\ttext: string\n}\n\n// Participant-facing payment summary the finalise endpoint returns on the FIRST\n// call. Drives the finished screen: `qualified` picks the complete vs ended-early\n// layout, `reward` is the formatted headline (e.g. \"$15\"), `payoutEmail` seeds the\n// one-tap payout default, tasksDone/tasksTotal drive the ended-early missing line.\n// Every field is optional so an older server (no `payment` block) degrades to the\n// neutral \"thanks, saved\" screen rather than throwing.\nexport interface PaymentSummary {\n\tqualified: boolean\n\treward: string | null\n\tpayoutEmail: string | null\n\ttasksDone: number\n\ttasksTotal: number\n}\n\nexport interface FinaliseResult {\n\tok: boolean\n\t// Only present on the first finalise call (the server computes it once).\n\tpayment: PaymentSummary | null\n}\n\nexport interface PostNoteResult {\n\tok: boolean\n\tid?: string\n\ttransient: boolean // true on network error / 5xx — eligible for retry\n}\n\nexport interface ThanksOptions {\n\t// Payment summary from the first finalise response. Null when the server is\n\t// older / didn't return it: we fall back to the neutral note-only screen.\n\tpayment: PaymentSummary | null\n\t// Confirm the participant's payout destination. Resolves true on success.\n\tonPayout: (destination: string | null) => Promise<boolean>\n\t// End-of-test wrap-up note (complete path). Throws on failure so the UI can retry.\n\tonSubmitNote: (text: string) => Promise<void> | void\n\tonSkip: () => void\n\t// Re-arm recording and dismiss the overlay (ended-early path \"Resume\").\n\tonResume: () => void\n}\n\n// Live monitor: wires an AnalyserNode onto the stream and polls it. Calls\n// `onChange(silent)` only on transitions (silent <-> audible), debounced by\n// SILENCE_SUSTAINED_MS so a brief between-words dip never flips the pill.\n// Returns a teardown that closes the AudioContext and disconnects nodes.\nexport interface SilenceMonitor {\n\tstop(): void\n}\n\n// ---------------------------------------------------------------------------\n// Silent-microphone guard thresholds\n//\n// A dead mic, or a virtual audio input device that macOS hands Chrome (e.g.\n// \"Background Music\", a Zoom/Teams virtual mic), delivers digital silence.\n// getUserMedia succeeds, MediaRecorder records, and 15 minutes of nothing\n// reaches the researcher with no warning. We DETECT a silent input stream and\n// WARN the participant (non-blocking: recording always continues).\n\n// Threshold rationale (dBFS, full-scale = 0):\n// - True failure is essentially digital silence: every sample ~0, so RMS is\n// -Infinity (or, with analyser float error, well below -80 dB).\n// - Confirmed-real speech in our captured data sits around -36 dB RMS.\n// - Quiet-but-present speech (a soft talker) sits around -40 to -50 dB.\n// We set the bar at -60 dB: only treat the stream as silent when RMS is\n// effectively zero. A -50 dB quiet voice is a full 10 dB ABOVE the line and\n// will never trip it, so we never falsely stop a real (quiet) participant.\n// This is deliberately conservative per the product decision: warn, never\n// block, and never false-positive on a real voice.\nexport const SILENCE_RMS_DB_THRESHOLD = -60\n\n// dBFS for a fully-silent (all-zero) window is -Infinity. Floor it to a finite\n// value so the pure decision function stays total and testable.\nexport const SILENCE_FLOOR_DB = -100\n\n// How long the analyser must read continuous silence before we surface the\n// warning. ~1.8s at record start and as the sustained-silence window during\n// recording. Long enough that a natural pause between sentences (which dips\n// toward the floor for a fraction of a second) never trips it; short enough\n// that a dead device is flagged almost immediately.\nexport const SILENCE_SUSTAINED_MS = 1800\n\n// How often the monitor samples the analyser.\nexport const SILENCE_POLL_MS = 250\n\n// Inline SVGs kept tiny. currentColor so they inherit the chip text color.\nexport const MIC_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\"><path d=\"M8 1.5a2 2 0 0 0-2 2v4a2 2 0 1 0 4 0v-4a2 2 0 0 0-2-2Z\" fill=\"currentColor\"/><path d=\"M4 7.5a4 4 0 0 0 8 0M8 11.5v3M5.5 14.5h5\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>`\nexport const MIC_MUTED_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\"><path d=\"M8 1.5a2 2 0 0 0-2 2v3.2L10 11V3.5a2 2 0 0 0-2-2Z\" fill=\"currentColor\"/><path d=\"M4 7.5a4 4 0 0 0 6.5 3.12M12 7.5a4 4 0 0 1-.3 1.5M8 11.5v3M5.5 14.5h5M2 2l12 12\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"/></svg>`\nexport const NOTE_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\"><path d=\"M3 3.5A1.5 1.5 0 0 1 4.5 2h7A1.5 1.5 0 0 1 13 3.5V10a1.5 1.5 0 0 1-1.5 1.5H7L4 14v-2.5h-.5A1.5 1.5 0 0 1 2 10V3.5A1.5 1.5 0 0 1 3.5 3\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\n// Thanks-screen icons: bold tick (medallion + done rows) and a clock (ended early).\nexport const TICK_ICON_SVG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M5 12.5 10 17.5 19 7\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\nexport const TICK_SM_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M3.5 8.5 6.5 11.5 12.5 5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\nexport const CLOCK_ICON_SVG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><circle cx=\"12\" cy=\"12\" r=\"8.4\" stroke=\"currentColor\" stroke-width=\"2\"/><path d=\"M12 7.5V12l3 2\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\nexport const SPARK_ICON_SVG = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8 1.5 9.5 6.5 14.5 8 9.5 9.5 8 14.5 6.5 9.5 1.5 8 6.5 6.5Z\" fill=\"currentColor\"/></svg>`\n// Calm \"wrapped up\" mark for the terminal ended-session screen: a finish flag,\n// not a celebratory tick (this is a neutral end state, not a passing reward).\nexport const FLAG_ICON_SVG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M6 21V4M6 4.5h9.5l-1.6 3.2 1.6 3.2H6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`\n\n// Re-exported for convenience where modules need the logger type.\nexport type Logger = PluginContext['logger']\n","// Session lifecycle + persistence for the user-test plugin: the localStorage\n// resume state (read/write/clear/persist), URL param parsing (slug + adopt id),\n// tester-name lookup, and every SaaS network call (create / adopt / finalise /\n// payout / notes). No DOM here. Pure data + fetch.\n\nimport { isValidSdkSessionId } from '../../identity'\nimport {\n\tACTIVE_SESSION_MAX_AGE_MS,\n\tACTIVE_SESSION_STORAGE_KEY,\n\ttype ActiveSessionState,\n\ttype AdoptResult,\n\ttype FinaliseNote,\n\ttype FinaliseResult,\n\ttype Logger,\n\ttype MutedSegment,\n\ttype PaymentSummary,\n\ttype PostNoteResult,\n\tRESUME_MAX_IDLE_MS,\n\ttype RecorderStore,\n\tTESTER_NAME_STORAGE_KEY,\n\ttype UserTestTask,\n} from './shared'\n\nexport function parseActiveSession(raw: unknown): ActiveSessionState | null {\n\tif (typeof raw !== 'object' || raw === null) return null\n\tconst s = raw as {\n\t\tslug?: unknown\n\t\tsessionId?: unknown\n\t\tnextChunkIndex?: unknown\n\t\tstartedAt?: unknown\n\t\tstatus?: unknown\n\t\tsdkSessionId?: unknown\n\t\tpausedAt?: unknown\n\t}\n\tif (typeof s.slug !== 'string' || !s.slug) return null\n\tif (typeof s.sessionId !== 'string' || !s.sessionId) return null\n\tif (typeof s.nextChunkIndex !== 'number' || !Number.isInteger(s.nextChunkIndex) || s.nextChunkIndex < 0) return null\n\tif (typeof s.startedAt !== 'number' || !Number.isFinite(s.startedAt)) return null\n\tconst status = s.status === 'paused' ? 'paused' : 'active'\n\tconst result: ActiveSessionState = {\n\t\tslug: s.slug,\n\t\tsessionId: s.sessionId,\n\t\tnextChunkIndex: s.nextChunkIndex,\n\t\tstartedAt: s.startedAt,\n\t\tstatus,\n\t}\n\t// Loose sanity filter, same shape the core uses for the id. A bad value is\n\t// dropped (resume still works for audio, only the replay link is at risk).\n\tif (typeof s.sdkSessionId === 'string' && isValidSdkSessionId(s.sdkSessionId)) {\n\t\tresult.sdkSessionId = s.sdkSessionId\n\t}\n\tif (typeof s.pausedAt === 'number' && Number.isFinite(s.pausedAt)) {\n\t\tresult.pausedAt = s.pausedAt\n\t}\n\treturn result\n}\n\n// Read the persisted active session for this origin, or null when absent,\n// unparseable, or stale (> ACTIVE_SESSION_MAX_AGE_MS). Storage access is wrapped\n// because localStorage can throw in sandboxed iframes / lockdown modes; a throw\n// must never break the plugin (we just don't resume).\nexport function readActiveSession(): ActiveSessionState | null {\n\ttry {\n\t\tconst raw = window.localStorage?.getItem(ACTIVE_SESSION_STORAGE_KEY)\n\t\tif (!raw) return null\n\t\tconst parsed = parseActiveSession(JSON.parse(raw))\n\t\tif (!parsed) return null\n\t\tif (Date.now() - parsed.startedAt > ACTIVE_SESSION_MAX_AGE_MS) {\n\t\t\tclearActiveSession()\n\t\t\treturn null\n\t\t}\n\t\t// Idle gate: a paused session (the page hid mid-test) is only resumable\n\t\t// for RESUME_MAX_IDLE_MS after the pause. Beyond that, treat the test as\n\t\t// abandoned so an unrelated return to this origin (within the 2h\n\t\t// startedAt cap, which never refreshes) can't silently re-adopt it and\n\t\t// re-acquire the mic. startedAt stays the duration anchor; pausedAt is the\n\t\t// idle clock.\n\t\tif (parsed.status === 'paused' && typeof parsed.pausedAt === 'number') {\n\t\t\tif (Date.now() - parsed.pausedAt > RESUME_MAX_IDLE_MS) {\n\t\t\t\tclearActiveSession()\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\t\treturn parsed\n\t} catch {\n\t\treturn null\n\t}\n}\n\nexport function writeActiveSession(state: ActiveSessionState): void {\n\ttry {\n\t\twindow.localStorage?.setItem(ACTIVE_SESSION_STORAGE_KEY, JSON.stringify(state))\n\t} catch {\n\t\t// Storage full / blocked: resume resilience is lost but recording continues.\n\t}\n}\n\nexport function clearActiveSession(): void {\n\ttry {\n\t\twindow.localStorage?.removeItem(ACTIVE_SESSION_STORAGE_KEY)\n\t} catch {\n\t\t// ignore\n\t}\n}\n\n// Persist the current store as the active resume state. Called when recording\n// starts and after the chunk index advances. No-op until the session id exists.\nexport function persistActiveSession(store: RecorderStore, status: 'active' | 'paused'): void {\n\tif (!store.sessionId) return\n\tconst state: ActiveSessionState = {\n\t\tslug: store.slug,\n\t\tsessionId: store.sessionId,\n\t\tnextChunkIndex: store.chunkIndex,\n\t\tstartedAt: store.startedAt,\n\t\tstatus,\n\t}\n\tif (store.sdkSessionId) state.sdkSessionId = store.sdkSessionId\n\t// Stamp pausedAt when the page hides mid-test so resume eligibility can be\n\t// gated on idle time (see RESUME_MAX_IDLE_MS in readActiveSession). An\n\t// 'active' write (recording start / chunk index advance) intentionally omits\n\t// pausedAt: the participant is live, so the idle clock should be cleared.\n\tif (status === 'paused') {\n\t\tstate.pausedAt = Date.now()\n\t}\n\twriteActiveSession(state)\n}\n\nexport function readTesterName(override: string): string | undefined {\n\tif (override) return override\n\ttry {\n\t\tconst stored = window.localStorage?.getItem(TESTER_NAME_STORAGE_KEY)\n\t\tif (stored && stored.trim()) return stored.trim().slice(0, 120)\n\t} catch {\n\t\t// Storage access can throw in some sandboxed iframes — ignore.\n\t}\n\treturn undefined\n}\n\n// Read the `uts` (user-test session) id the entry screen appends when it\n// creates the session server-side. When present, the SDK ADOPTS that session\n// instead of minting its own (so the session has the participant's email from\n// creation, no double-session). Absent for open tests using the old link\n// shape, where the SDK falls back to createSession.\nexport function getAdoptSessionId(): string | null {\n\tif (typeof window === 'undefined' || typeof window.location === 'undefined') return null\n\ttry {\n\t\tconst params = new URLSearchParams(window.location.search)\n\t\tconst raw = params.get('uts')\n\t\tif (!raw) return null\n\t\tconst cleaned = raw.trim().slice(0, 64)\n\t\t// Session ids are cuids: lowercase alphanumerics. Reject anything else.\n\t\tif (!/^[a-z0-9]+$/i.test(cleaned)) return null\n\t\treturn cleaned\n\t} catch {\n\t\treturn null\n\t}\n}\n\nexport function getTestSlug(queryParam: string): string | null {\n\tif (typeof window === 'undefined' || typeof window.location === 'undefined') return null\n\ttry {\n\t\tconst params = new URLSearchParams(window.location.search)\n\t\tconst slug = params.get(queryParam)\n\t\tif (!slug) return null\n\t\tconst cleaned = slug.trim().slice(0, 64)\n\t\tif (!/^[a-z0-9-]+$/i.test(cleaned)) return null\n\t\treturn cleaned\n\t} catch {\n\t\treturn null\n\t}\n}\n\nexport function parseTasks(raw: unknown): UserTestTask[] {\n\tif (!Array.isArray(raw)) return []\n\tconst out = raw.flatMap((item: unknown): UserTestTask[] => {\n\t\tconst t = item as { id?: unknown; prompt?: unknown; sortOrder?: unknown }\n\t\tif (!t || typeof t.id !== 'string' || typeof t.prompt !== 'string' || typeof t.sortOrder !== 'number') return []\n\t\treturn [{ id: t.id, prompt: t.prompt, sortOrder: t.sortOrder }]\n\t})\n\tout.sort((a, b) => a.sortOrder - b.sortOrder)\n\treturn out\n}\n\nexport async function createSession(\n\tapiUrl: string,\n\tslug: string,\n\ttesterName: string | undefined,\n): Promise<{ sessionId: string; clientId: string; tasks: UserTestTask[] } | null> {\n\ttry {\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ slug, ...(testerName ? { testerName } : {}) }),\n\t\t})\n\t\tif (!res.ok) return null\n\t\tconst json = (await res.json()) as { sessionId?: unknown; clientId?: unknown; tasks?: unknown }\n\t\tif (typeof json.sessionId !== 'string' || typeof json.clientId !== 'string') return null\n\t\treturn { sessionId: json.sessionId, clientId: json.clientId, tasks: parseTasks(json.tasks) }\n\t} catch {\n\t\treturn null\n\t}\n}\n\n// Adopt an existing session the entry screen already created (carried via the\n// `uts` URL param). GET the clientId + tasks for it; we do NOT create a new\n// session. Returns null on any failure so the caller can surface the error\n// state (we deliberately do NOT silently fall back to createSession here: a\n// present-but-unresolvable uts means something is wrong, and creating a second\n// anonymous session is exactly the double-session bug we're avoiding).\nexport async function adoptSession(apiUrl: string, sessionId: string): Promise<AdoptResult> {\n\ttry {\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/adopt`, {\n\t\t\tmethod: 'GET',\n\t\t})\n\t\t// 409 Conflict / 410 Gone => the session is closed. Don't resurrect it.\n\t\tif (res.status === 409 || res.status === 410) return { kind: 'closed' }\n\t\tif (!res.ok) return { kind: 'error' }\n\t\tconst json = (await res.json()) as { sessionId?: unknown; clientId?: unknown; tasks?: unknown }\n\t\tif (typeof json.sessionId !== 'string' || typeof json.clientId !== 'string') return { kind: 'error' }\n\t\treturn { kind: 'ok', sessionId: json.sessionId, clientId: json.clientId, tasks: parseTasks(json.tasks) }\n\t} catch {\n\t\treturn { kind: 'error' }\n\t}\n}\n\nexport function parsePaymentSummary(raw: unknown): PaymentSummary | null {\n\tif (typeof raw !== 'object' || raw === null) return null\n\tconst p = raw as {\n\t\tqualified?: unknown\n\t\treward?: unknown\n\t\tpayoutEmail?: unknown\n\t\ttasksDone?: unknown\n\t\ttasksTotal?: unknown\n\t}\n\tif (typeof p.qualified !== 'boolean') return null\n\treturn {\n\t\tqualified: p.qualified,\n\t\treward: typeof p.reward === 'string' ? p.reward : null,\n\t\tpayoutEmail: typeof p.payoutEmail === 'string' ? p.payoutEmail : null,\n\t\ttasksDone: typeof p.tasksDone === 'number' ? p.tasksDone : 0,\n\t\ttasksTotal: typeof p.tasksTotal === 'number' ? p.tasksTotal : 0,\n\t}\n}\n\nexport async function finaliseSession(\n\tapiUrl: string,\n\tsessionId: string,\n\tdurationSeconds: number,\n\textras: {\n\t\tmutedSegments?: MutedSegment[]\n\t\tendNote?: string | null\n\t\tnotes?: FinaliseNote[]\n\t\t// Replay linkage. sdkSessionId is the primary, always-available key:\n\t\t// the server resolves the SessionReplay by (clientId + sdkSessionId)\n\t\t// and sets UserTestSession.sessionReplayId. replayOffsetMs is the\n\t\t// offset captured at session start, only present when replay was\n\t\t// active. Both optional so older servers tolerate their absence and a\n\t\t// test with no replay still finalises cleanly.\n\t\tsdkSessionId?: string\n\t\treplayOffsetMs?: number\n\t} = {},\n): Promise<FinaliseResult> {\n\ttry {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tdurationSeconds: Math.max(0, Math.round(durationSeconds)),\n\t\t}\n\t\tif (extras.mutedSegments && extras.mutedSegments.length > 0) {\n\t\t\tbody.mutedSegments = extras.mutedSegments\n\t\t}\n\t\tconst trimmedEndNote = extras.endNote?.trim()\n\t\tif (trimmedEndNote) body.endNote = trimmedEndNote\n\t\tif (extras.notes && extras.notes.length > 0) {\n\t\t\t// Server caps at 200; trim defensively here too.\n\t\t\tbody.notes = extras.notes.slice(0, 200).map(n => ({\n\t\t\t\tatMs: Math.max(0, Math.round(n.atMs)),\n\t\t\t\ttext: n.text,\n\t\t\t}))\n\t\t}\n\t\tif (extras.sdkSessionId) body.sdkSessionId = extras.sdkSessionId\n\t\tif (typeof extras.replayOffsetMs === 'number') {\n\t\t\tbody.replayOffsetMs = Math.max(0, Math.round(extras.replayOffsetMs))\n\t\t}\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/finalise`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tkeepalive: true,\n\t\t})\n\t\tif (!res.ok) return { ok: false, payment: null }\n\t\tlet payment: PaymentSummary | null = null\n\t\ttry {\n\t\t\tconst json = (await res.json()) as { payment?: unknown }\n\t\t\tpayment = parsePaymentSummary(json.payment)\n\t\t} catch {\n\t\t\t// Older server or non-JSON body: degrade to neutral thanks screen.\n\t\t}\n\t\treturn { ok: true, payment }\n\t} catch {\n\t\treturn { ok: false, payment: null }\n\t}\n}\n\n// POST the participant's payout destination to the SaaS side. Best-effort with a\n// single retry; the destination defaults server-side to the testerEmail when we\n// send only `method`, so a dropped call still leaves a payable session. Returns\n// ok so the UI can confirm or surface a soft error.\nexport async function postPayout(\n\tapiUrl: string,\n\tsessionId: string,\n\tdestination: string | null,\n\tlogger: Logger,\n): Promise<boolean> {\n\tconst url = `${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/payout`\n\tconst body: Record<string, unknown> = { method: 'email' }\n\tif (destination) body.destination = destination\n\tfor (let attempt = 0; attempt < 2; attempt += 1) {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\tkeepalive: true,\n\t\t\t})\n\t\t\tif (res.ok) return true\n\t\t\t// 4xx won't improve on retry (bad email, etc.).\n\t\t\tif (res.status >= 400 && res.status < 500) {\n\t\t\t\tlogger.warn(`payout rejected with ${res.status}`)\n\t\t\t\treturn false\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlogger.warn(`payout attempt ${attempt + 1} failed`, err)\n\t\t}\n\t\tawait new Promise(resolve => setTimeout(resolve, 400 + Math.floor(Math.random() * 200)))\n\t}\n\treturn false\n}\n\nexport async function postNoteOnce(\n\tapiUrl: string,\n\tsessionId: string,\n\tatMs: number,\n\ttext: string,\n\tlogger: Logger,\n): Promise<PostNoteResult> {\n\ttry {\n\t\tconst res = await fetch(`${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/notes`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ atMs: Math.max(0, Math.round(atMs)), text }),\n\t\t\tkeepalive: true,\n\t\t})\n\t\tif (!res.ok) {\n\t\t\tlogger.warn(`note POST rejected with ${res.status}`)\n\t\t\treturn { ok: false, transient: res.status >= 500 || res.status === 408 || res.status === 429 }\n\t\t}\n\t\t// Best-effort id extraction; failures here don't matter for ack.\n\t\tlet id: string | undefined\n\t\ttry {\n\t\t\tconst json = (await res.json()) as { id?: unknown }\n\t\t\tif (typeof json.id === 'string') id = json.id\n\t\t} catch { /* ignore */ }\n\t\treturn { ok: true, id, transient: false }\n\t} catch (err) {\n\t\tlogger.warn('note POST failed', err)\n\t\treturn { ok: false, transient: true }\n\t}\n}\n\n// One immediate retry on transient errors. If still failing, defer to\n// finalise batching via the un-acked notes channel.\nexport async function postNoteWithRetry(\n\tapiUrl: string,\n\tsessionId: string,\n\tatMs: number,\n\ttext: string,\n\tlogger: Logger,\n): Promise<PostNoteResult> {\n\tconst first = await postNoteOnce(apiUrl, sessionId, atMs, text, logger)\n\tif (first.ok || !first.transient) return first\n\tawait new Promise(resolve => setTimeout(resolve, 400 + Math.floor(Math.random() * 200)))\n\treturn postNoteOnce(apiUrl, sessionId, atMs, text, logger)\n}\n","// All on-page UI for the user-test plugin: the floating indicator/control bar,\n// the tasks panel, the mic chip state machine, the notes popover, the mute /\n// resumed toasts, and the finished-screen overlays (complete, ended-early,\n// session-ended). Shadow-DOM scoped so host CSS can't leak in. No React, no\n// network: every function here reads/writes the DOM and the shared store.\n\nimport {\n\tCLOCK_ICON_SVG,\n\tFLAG_ICON_SVG,\n\ttype IndicatorCallbacks,\n\tMIC_ICON_SVG,\n\tMIC_MUTED_ICON_SVG,\n\tNOTE_ICON_SVG,\n\ttype RecorderStore,\n\tSPARK_ICON_SVG,\n\tTASKS_PANEL_OPEN_STORAGE_KEY,\n\ttype ThanksOptions,\n\tTICK_ICON_SVG,\n\tTICK_SM_SVG,\n} from './shared'\n\nexport function buildIndicator(host: HTMLElement, store: RecorderStore, callbacks: IndicatorCallbacks): ShadowRoot {\n\tconst root = host.attachShadow({ mode: 'open' })\n\tconst style = document.createElement('style')\n\t// Compact, glassy dark pill. Mic chip is now a real button with three\n\t// states (recording / muted / no-mic). Notes button sits beside it.\n\tstyle.textContent = `\n\t\t:host { all: initial; }\n\t\t.anchor {\n\t\t\tposition: fixed;\n\t\t\tbottom: calc(env(safe-area-inset-bottom, 0px) + 16px);\n\t\t\tleft: 50%; transform: translateX(-50%);\n\t\t\tdisplay: flex; flex-direction: column; align-items: center; gap: 8px;\n\t\t\tz-index: 2147483646; max-width: calc(100vw - 32px);\n\t\t\tfont: 13px/1 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n\t\t\tcolor: #fff;\n\t\t}\n\t\t.bar {\n\t\t\tdisplay: inline-flex; align-items: center; gap: 6px;\n\t\t\tpadding: 6px 8px 6px 6px;\n\t\t\tbackground: rgba(17,17,17,0.82);\n\t\t\tborder: 1px solid rgba(255,255,255,0.08);\n\t\t\tborder-radius: 999px;\n\t\t\tbox-shadow: 0 8px 24px rgba(0,0,0,0.22);\n\t\t\tbackdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);\n\t\t\tmax-width: 100%;\n\t\t}\n\t\t.panel {\n\t\t\tbackground: rgba(17,17,17,0.92);\n\t\t\tborder: 1px solid rgba(255,255,255,0.08);\n\t\t\tborder-radius: 14px; padding: 12px 14px 12px 8px;\n\t\t\tline-height: 1.45;\n\t\t\tbox-shadow: 0 12px 32px rgba(0,0,0,0.32);\n\t\t\tmax-height: min(60vh, 480px);\n\t\t\tmax-width: min(420px, calc(100vw - 32px));\n\t\t\twidth: max-content; overflow-y: auto;\n\t\t}\n\t\t.panel[hidden] { display: none; }\n\t\t.panel ol { margin: 0; padding-left: 26px; }\n\t\t.panel li { margin: 0 0 8px; }\n\t\t.panel li:last-child { margin: 0; }\n\n\t\t/* Mic chip: pill-within-pill with dot + label, doubles as mute toggle. */\n\t\t.mic {\n\t\t\tdisplay: inline-flex; align-items: center; gap: 7px;\n\t\t\tmin-height: 32px; min-width: 44px;\n\t\t\tpadding: 0 11px 0 10px;\n\t\t\tborder-radius: 999px;\n\t\t\tbackground: rgba(255,255,255,0.06);\n\t\t\tborder: 1px solid rgba(255,255,255,0.06);\n\t\t\tcolor: #fff; font: inherit;\n\t\t\tcursor: pointer; appearance: none;\n\t\t\ttransition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\t\t}\n\t\t.mic:hover { background: rgba(255,255,255,0.12); }\n\t\t.mic:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\t\t.mic[data-mic-state=\"muted\"] {\n\t\t\tbackground: rgba(251, 191, 36, 0.18);\n\t\t\tborder-color: rgba(251, 191, 36, 0.45);\n\t\t\tcolor: #fcd34d;\n\t\t}\n\t\t.mic[data-mic-state=\"muted\"]:hover { background: rgba(251, 191, 36, 0.26); }\n\t\t/* Connecting: getUserMedia pending. Steady amber tint reads as \"working\",\n\t\t distinct from the live red pulse and the failed state below. The icon\n\t\t gets a gentle non-pulsing breathe so it feels alive without alarming. */\n\t\t.mic[data-mic-state=\"connecting\"] {\n\t\t\tbackground: rgba(251, 191, 36, 0.14);\n\t\t\tborder-color: rgba(251, 191, 36, 0.32);\n\t\t\tcolor: #fcd34d;\n\t\t\tcursor: default;\n\t\t}\n\t\t.mic[data-mic-state=\"connecting\"]:hover { background: rgba(251, 191, 36, 0.14); }\n\t\t.mic[data-mic-state=\"connecting\"] .mic-icon {\n\t\t\tcolor: #fbbf24;\n\t\t\tanimation: micBreathe 1.4s ease-in-out infinite;\n\t\t}\n\t\t/* Failed terminal state, actionable. Tappable affordance: clearer border,\n\t\t pointer cursor, brightens on hover/focus to invite the retry tap. */\n\t\t.mic[data-mic-state=\"none\"] {\n\t\t\tbackground: rgba(255,255,255,0.05);\n\t\t\tborder-color: rgba(255,255,255,0.14);\n\t\t\tcolor: rgba(255,255,255,0.72);\n\t\t\tcursor: pointer;\n\t\t}\n\t\t.mic[data-mic-state=\"none\"]:hover {\n\t\t\tbackground: rgba(255,255,255,0.12);\n\t\t\tborder-color: rgba(255,255,255,0.24);\n\t\t\tcolor: #fff;\n\t\t}\n\t\t@keyframes micBreathe {\n\t\t\t0%, 100% { opacity: 0.55; }\n\t\t\t50% { opacity: 1; }\n\t\t}\n\t\t.mic-icon { width: 13px; height: 13px; display: inline-block; flex-shrink: 0; }\n\t\t.mic-label { font-weight: 500; letter-spacing: 0.01em; white-space: nowrap; }\n\n\t\t.dot {\n\t\t\twidth: 7px; height: 7px; border-radius: 50%;\n\t\t\tbackground: #ef4444;\n\t\t\tbox-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6);\n\t\t\tanimation: pulse 1.6s ease-out infinite;\n\t\t\tflex-shrink: 0;\n\t\t}\n\t\t.dot[data-state=\"no-audio\"] { background: #fbbf24; animation: none; }\n\t\t.dot[data-state=\"finishing\"] { background: #fbbf24; animation: none; }\n\t\t.dot[data-state=\"done\"] { background: #10b981; animation: none; }\n\t\t.dot[data-state=\"error\"] { background: #ef4444; animation: none; }\n\n\t\t.btn {\n\t\t\tappearance: none; border: 0; background: rgba(255,255,255,0.10);\n\t\t\tcolor: #fff; font: inherit; font-weight: 600;\n\t\t\tpadding: 6px 12px; min-height: 32px; border-radius: 999px; cursor: pointer;\n\t\t\ttransition: background 0.15s ease, transform 0.06s ease;\n\t\t\tdisplay: inline-flex; align-items: center; gap: 6px;\n\t\t}\n\t\t.btn:hover { background: rgba(255,255,255,0.20); }\n\t\t.btn:active { transform: scale(0.97); }\n\t\t.btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\t\t.btn[disabled] { opacity: 0.5; cursor: progress; }\n\t\t.tasks-btn[aria-expanded=\"true\"] { background: rgba(255,255,255,0.24); }\n\n\t\t/* Note button: icon-only, matches mic chip footprint */\n\t\t.note-btn {\n\t\t\twidth: 32px; min-height: 32px; padding: 0;\n\t\t\tbackground: rgba(255,255,255,0.06);\n\t\t\tborder: 1px solid rgba(255,255,255,0.06);\n\t\t\tborder-radius: 999px;\n\t\t\tdisplay: inline-flex; align-items: center; justify-content: center; gap: 4px;\n\t\t\tcolor: #fff; font: inherit; cursor: pointer; appearance: none;\n\t\t\ttransition: background 0.15s ease, border-color 0.15s ease, width 0.18s ease;\n\t\t\toverflow: hidden;\n\t\t}\n\t\t.note-btn:hover { background: rgba(255,255,255,0.14); }\n\t\t.note-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\t\t.note-btn[data-has-notes=\"true\"] { width: auto; padding: 0 10px 0 9px; gap: 6px; }\n\t\t.note-btn[aria-expanded=\"true\"] { background: rgba(255,255,255,0.22); border-color: rgba(255,255,255,0.18); }\n\t\t.note-icon { width: 14px; height: 14px; display: inline-block; }\n\t\t.note-count { font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }\n\n\t\t.spacer { width: 1px; height: 18px; background: rgba(255,255,255,0.14); margin: 0 1px; }\n\n\t\t@media (max-width: 480px) {\n\t\t\t.bar { gap: 4px; padding: 5px 6px 5px 5px; }\n\t\t\t.btn { padding: 7px 12px; min-height: 38px; }\n\t\t\t.mic, .note-btn { min-height: 38px; }\n\t\t\t.note-btn { width: 38px; }\n\t\t\t.note-btn[data-has-notes=\"true\"] { width: auto; }\n\t\t}\n\n\t\t/* First-mute helper toast: sits above the pill, auto-dismisses */\n\t\t.toast {\n\t\t\tbackground: rgba(17,17,17,0.92);\n\t\t\tborder: 1px solid rgba(251, 191, 36, 0.45);\n\t\t\tcolor: #fff;\n\t\t\tpadding: 9px 14px; border-radius: 12px;\n\t\t\tmax-width: min(340px, calc(100vw - 32px));\n\t\t\tbox-shadow: 0 12px 28px rgba(0,0,0,0.28);\n\t\t\ttext-align: center; line-height: 1.4;\n\t\t\tanimation: toast-in 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);\n\t\t}\n\t\t.toast[data-leaving=\"true\"] { animation: toast-out 0.24s ease forwards; }\n\t\t.toast strong { color: #fcd34d; font-weight: 600; }\n\t\t@keyframes toast-in {\n\t\t\tfrom { opacity: 0; transform: translateY(6px); }\n\t\t\tto { opacity: 1; transform: translateY(0); }\n\t\t}\n\t\t@keyframes toast-out {\n\t\t\tto { opacity: 0; transform: translateY(4px); }\n\t\t}\n\n\t\t/* \"Recording resumed\" confirmation: same pill footprint as the mute toast,\n\t\t but carries the live-record red accent (not the amber warning treatment)\n\t\t so it reads as reassurance, not a problem. Compact, inline, auto-dismisses.\n\t\t Leads with the same pulsing record dot used on the bar's mic chip. */\n\t\t.resume-toast {\n\t\t\tdisplay: inline-flex; align-items: center; gap: 8px;\n\t\t\tbackground: rgba(17,17,17,0.92);\n\t\t\tborder: 1px solid rgba(239, 68, 68, 0.42);\n\t\t\tcolor: #fff; font-weight: 500; letter-spacing: 0.01em;\n\t\t\tpadding: 8px 13px; border-radius: 999px;\n\t\t\tbox-shadow: 0 12px 28px rgba(0,0,0,0.28);\n\t\t\twhite-space: nowrap;\n\t\t\tanimation: toast-in 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);\n\t\t}\n\t\t.resume-toast[data-leaving=\"true\"] { animation: toast-out 0.24s ease forwards; }\n\t\t.resume-toast .dot {\n\t\t\twidth: 7px; height: 7px; border-radius: 50%;\n\t\t\tbackground: #ef4444; flex-shrink: 0;\n\t\t\tbox-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6);\n\t\t\tanimation: pulse 1.6s ease-out infinite;\n\t\t}\n\n\t\t/* Notes popover */\n\t\t.note-popover {\n\t\t\tbackground: rgba(17,17,17,0.94);\n\t\t\tborder: 1px solid rgba(255,255,255,0.10);\n\t\t\tborder-radius: 14px; padding: 12px;\n\t\t\twidth: min(340px, calc(100vw - 32px));\n\t\t\tbox-shadow: 0 18px 40px rgba(0,0,0,0.36);\n\t\t\tdisplay: flex; flex-direction: column; gap: 10px;\n\t\t\tanimation: pop-in 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);\n\t\t}\n\t\t.note-popover[hidden] { display: none; }\n\t\t@keyframes pop-in {\n\t\t\tfrom { opacity: 0; transform: translateY(6px) scale(0.98); }\n\t\t\tto { opacity: 1; transform: translateY(0) scale(1); }\n\t\t}\n\t\t.note-head {\n\t\t\tcolor: rgba(255,255,255,0.7); font-size: 12px;\n\t\t\tfont-weight: 500; letter-spacing: 0.02em;\n\t\t}\n\t\t.note-textarea {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tmin-height: 80px; resize: vertical;\n\t\t\tpadding: 10px 11px;\n\t\t\tbackground: rgba(0,0,0,0.35);\n\t\t\tborder: 1px solid rgba(255,255,255,0.10);\n\t\t\tborder-radius: 10px;\n\t\t\tcolor: #fff; font: inherit; font-size: 13.5px;\n\t\t\tline-height: 1.45;\n\t\t\ttransition: border-color 0.15s ease;\n\t\t}\n\t\t.note-textarea:focus { outline: none; border-color: rgba(255,255,255,0.32); }\n\t\t.note-textarea::placeholder { color: rgba(255,255,255,0.42); }\n\t\t.note-actions {\n\t\t\tdisplay: flex; align-items: center; justify-content: space-between; gap: 8px;\n\t\t}\n\t\t.note-actions .hint {\n\t\t\tcolor: rgba(255,255,255,0.45); font-size: 11px;\n\t\t}\n\t\t.note-actions .group { display: inline-flex; gap: 6px; }\n\t\t.note-actions .btn { padding: 6px 12px; font-size: 12.5px; min-height: 32px; }\n\t\t.btn-primary { background: #fff !important; color: #111; }\n\t\t.btn-primary:hover { background: rgba(255,255,255,0.85) !important; }\n\t\t.btn-ghost { background: transparent; color: rgba(255,255,255,0.7); }\n\t\t.btn-ghost:hover { background: rgba(255,255,255,0.10); color: #fff; }\n\n\t\t/* ---- Finished screen (complete + ended-early). Usero warm-stone palette,\n\t\t shadow-DOM scoped so host CSS can't leak in. Scrollable so the primary\n\t\t action stays reachable on a short phone with the keyboard open. ---- */\n\t\t.thanks {\n\t\t\tposition: fixed; inset: 0;\n\t\t\tdisplay: flex; align-items: flex-start; justify-content: center;\n\t\t\tbackground: rgba(28, 25, 23, 0.62);\n\t\t\tbackdrop-filter: blur(8px);\n\t\t\t-webkit-backdrop-filter: blur(8px);\n\t\t\tcolor: #1c1917;\n\t\t\tfont-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n\t\t\tz-index: 2147483647;\n\t\t\tpadding: 24px 16px calc(env(safe-area-inset-bottom, 0px) + 24px);\n\t\t\toverflow-y: auto;\n\t\t\t-webkit-overflow-scrolling: touch;\n\t\t}\n\t\t.thanks-card {\n\t\t\tbackground: #fff; color: #1c1917;\n\t\t\tborder-radius: 22px; padding: 30px 24px 24px;\n\t\t\tmax-width: 400px; width: 100%;\n\t\t\tmargin: auto 0;\n\t\t\tbox-shadow: 0 24px 60px rgba(28, 25, 23, 0.28), 0 2px 8px rgba(28, 25, 23, 0.12);\n\t\t\ttext-align: left;\n\t\t\tanimation: thanks-in 0.34s cubic-bezier(0.16, 1, 0.3, 1);\n\t\t}\n\t\t@keyframes thanks-in {\n\t\t\tfrom { opacity: 0; transform: translateY(14px) scale(0.985); }\n\t\t\tto { opacity: 1; transform: translateY(0) scale(1); }\n\t\t}\n\t\t.thanks-card .head { text-align: center; }\n\t\t.thanks h2 {\n\t\t\tmargin: 0 0 7px; font-size: 22px; line-height: 1.2;\n\t\t\tfont-weight: 600; letter-spacing: -0.018em; color: #1c1917;\n\t\t}\n\t\t.thanks .lede {\n\t\t\tmargin: 0 auto 22px; font-size: 14.5px; line-height: 1.5;\n\t\t\tcolor: #57534e; text-align: center; max-width: 30ch;\n\t\t}\n\n\t\t/* Status medallion: green tick when complete, warm ring when ended early */\n\t\t.thanks .check {\n\t\t\twidth: 56px; height: 56px; border-radius: 50%;\n\t\t\tdisplay: grid; place-items: center;\n\t\t\tmargin: 0 auto 16px;\n\t\t}\n\t\t.thanks .check.ok {\n\t\t\tbackground: #ecfdf5;\n\t\t\tbox-shadow: inset 0 0 0 1px rgba(16,185,129,0.22);\n\t\t\tcolor: #059669;\n\t\t}\n\t\t.thanks .check.ok svg { width: 26px; height: 26px; }\n\t\t.thanks .check.early {\n\t\t\tbackground: #fff7ed;\n\t\t\tbox-shadow: inset 0 0 0 1px rgba(234,88,12,0.20);\n\t\t\tcolor: #ea580c;\n\t\t}\n\t\t.thanks .check.early svg { width: 24px; height: 24px; }\n\t\t.thanks .check.ended {\n\t\t\tbackground: #f5f5f4;\n\t\t\tbox-shadow: inset 0 0 0 1px rgba(120,113,108,0.20);\n\t\t\tcolor: #78716c;\n\t\t}\n\t\t.thanks .check.ended svg { width: 24px; height: 24px; }\n\n\t\t/* Verified-checks list (complete) / progress list (ended early) */\n\t\t.thanks .checks {\n\t\t\tlist-style: none; margin: 0 0 4px; padding: 0;\n\t\t\tborder: 1px solid #f0eeec; border-radius: 14px;\n\t\t\tbackground: #fafaf9; overflow: hidden;\n\t\t}\n\t\t.thanks .checks li {\n\t\t\tdisplay: flex; align-items: center; gap: 11px;\n\t\t\tpadding: 12px 14px; font-size: 14px; color: #292524;\n\t\t\tborder-top: 1px solid #f0eeec;\n\t\t}\n\t\t.thanks .checks li:first-child { border-top: 0; }\n\t\t.thanks .checks .ic {\n\t\t\twidth: 20px; height: 20px; border-radius: 50%;\n\t\t\tdisplay: grid; place-items: center; flex-shrink: 0;\n\t\t}\n\t\t.thanks .checks .ic.done { background: #d1fae5; color: #059669; }\n\t\t.thanks .checks .ic.todo { background: #f5f5f4; color: #a8a29e; box-shadow: inset 0 0 0 1px #e7e5e4; }\n\t\t.thanks .checks .ic svg { width: 12px; height: 12px; }\n\t\t.thanks .checks li.muted-row { color: #78716c; }\n\n\t\t/* Payout block (complete) */\n\t\t.thanks .payout { margin-top: 20px; }\n\t\t.thanks .payout-q {\n\t\t\tfont-size: 12px; font-weight: 600; letter-spacing: 0.04em;\n\t\t\ttext-transform: uppercase; color: #a8a29e;\n\t\t\tmargin: 0 0 10px;\n\t\t}\n\t\t.thanks .pay-primary {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tappearance: none; border: 0; cursor: pointer;\n\t\t\tbackground: #ea580c; color: #fff;\n\t\t\tpadding: 15px 18px; border-radius: 14px;\n\t\t\tfont: inherit; font-weight: 600; font-size: 15.5px;\n\t\t\tline-height: 1.3; text-align: center;\n\t\t\tbox-shadow: 0 6px 16px rgba(234, 88, 12, 0.28);\n\t\t\ttransition: background 0.15s ease, transform 0.07s ease, box-shadow 0.15s ease;\n\t\t}\n\t\t.thanks .pay-primary:hover { background: #c2410c; }\n\t\t.thanks .pay-primary:active { transform: scale(0.985); }\n\t\t.thanks .pay-primary:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; }\n\t\t.thanks .pay-primary[disabled] { opacity: 0.6; cursor: progress; box-shadow: none; }\n\t\t.thanks .pay-primary .amt { font-variant-numeric: tabular-nums; }\n\t\t.thanks .pay-alt {\n\t\t\tdisplay: block; width: 100%;\n\t\t\tmargin-top: 12px; padding: 4px;\n\t\t\tbackground: none; border: 0; cursor: pointer;\n\t\t\tfont: inherit; font-size: 13px; font-weight: 500;\n\t\t\tcolor: #78716c; text-align: center;\n\t\t\ttext-decoration: underline; text-underline-offset: 2px;\n\t\t\ttransition: color 0.15s ease;\n\t\t}\n\t\t.thanks .pay-alt:hover { color: #44403c; }\n\t\t.thanks .pay-alt:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; border-radius: 6px; }\n\t\t.thanks [hidden] { display: none !important; }\n\n\t\t/* Alternate-email expander */\n\t\t.thanks .pay-edit { margin-top: 14px; animation: pop-in 0.2s cubic-bezier(0.2,0.8,0.2,1); }\n\t\t.thanks .pay-edit[hidden] { display: none; }\n\t\t.thanks .pay-label {\n\t\t\tdisplay: block; margin: 0 0 7px;\n\t\t\tfont-size: 13px; font-weight: 500; color: #44403c;\n\t\t}\n\t\t.thanks .pay-input {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tpadding: 12px 13px;\n\t\t\tbackground: #fff; border: 1px solid #e7e5e4; border-radius: 11px;\n\t\t\tfont: inherit; font-size: 15px; color: #1c1917;\n\t\t\ttransition: border-color 0.15s ease, box-shadow 0.15s ease;\n\t\t}\n\t\t.thanks .pay-input:focus {\n\t\t\toutline: none; border-color: #ea580c;\n\t\t\tbox-shadow: 0 0 0 3px rgba(234, 88, 12, 0.16);\n\t\t}\n\t\t.thanks .pay-input::placeholder { color: #a8a29e; }\n\t\t.thanks .pay-eta {\n\t\t\tmargin: 14px 0 0; font-size: 12px; line-height: 1.45;\n\t\t\tcolor: #a8a29e; text-align: center;\n\t\t}\n\n\t\t/* Ended-early \"what unlocks the reward\" note */\n\t\t.thanks .early-note {\n\t\t\tdisplay: flex; align-items: flex-start; gap: 10px;\n\t\t\tmargin-top: 18px; padding: 13px 14px;\n\t\t\tbackground: #fff7ed; border: 1px solid #fed7aa; border-radius: 13px;\n\t\t\tfont-size: 13.5px; line-height: 1.45; color: #9a3412;\n\t\t}\n\t\t.thanks .early-note svg { width: 17px; height: 17px; flex-shrink: 0; margin-top: 1px; color: #ea580c; }\n\t\t.thanks .early-actions { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }\n\t\t.thanks .resume-btn {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tappearance: none; border: 0; cursor: pointer;\n\t\t\tbackground: #ea580c; color: #fff;\n\t\t\tpadding: 15px 18px; border-radius: 14px;\n\t\t\tfont: inherit; font-weight: 600; font-size: 15.5px;\n\t\t\tbox-shadow: 0 6px 16px rgba(234, 88, 12, 0.28);\n\t\t\ttransition: background 0.15s ease, transform 0.07s ease;\n\t\t}\n\t\t.thanks .resume-btn:hover { background: #c2410c; }\n\t\t.thanks .resume-btn:active { transform: scale(0.985); }\n\t\t.thanks .resume-btn:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; }\n\t\t.thanks .exit-btn {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tappearance: none; border: 0; background: none; cursor: pointer;\n\t\t\tpadding: 4px; font: inherit; font-size: 13px; line-height: 1.45;\n\t\t\tcolor: #78716c; text-align: center;\n\t\t}\n\t\t.thanks .exit-btn:hover { color: #44403c; }\n\t\t.thanks .exit-btn:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; border-radius: 6px; }\n\n\t\t/* End-of-test note (shown after payout is set, complete path only) */\n\t\t.thanks .note-section {\n\t\t\tmargin-top: 22px; padding-top: 20px;\n\t\t\tborder-top: 1px solid #f0eeec;\n\t\t}\n\t\t.thanks .end-label {\n\t\t\tdisplay: block; margin: 0 0 8px;\n\t\t\tfont-size: 13px; font-weight: 500; color: #44403c;\n\t\t}\n\t\t.thanks .end-textarea {\n\t\t\twidth: 100%; box-sizing: border-box;\n\t\t\tmin-height: 84px; resize: vertical;\n\t\t\tpadding: 12px 13px;\n\t\t\tbackground: #fafaf9;\n\t\t\tborder: 1px solid #e7e5e4;\n\t\t\tborder-radius: 12px;\n\t\t\tfont: inherit; font-size: 14.5px; line-height: 1.5;\n\t\t\tcolor: #1c1917;\n\t\t\ttransition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;\n\t\t}\n\t\t.thanks .end-textarea:focus {\n\t\t\toutline: none; border-color: #ea580c; background: #fff;\n\t\t\tbox-shadow: 0 0 0 3px rgba(234, 88, 12, 0.14);\n\t\t}\n\t\t.thanks .end-textarea::placeholder { color: #a8a29e; }\n\t\t.thanks .end-actions {\n\t\t\tdisplay: flex; gap: 10px; margin-top: 14px;\n\t\t}\n\t\t.thanks .end-actions button {\n\t\t\tflex: 1;\n\t\t\tappearance: none; border: 1px solid #e7e5e4;\n\t\t\tbackground: #fff; color: #44403c;\n\t\t\tpadding: 12px 14px; border-radius: 12px;\n\t\t\tfont: inherit; font-weight: 600; font-size: 14px;\n\t\t\tcursor: pointer;\n\t\t\ttransition: background 0.15s ease, border-color 0.15s ease;\n\t\t}\n\t\t.thanks .end-actions button:hover { background: #fafaf9; border-color: #d6d3d1; }\n\t\t.thanks .end-actions button.primary {\n\t\t\tbackground: #1c1917; color: #fff; border-color: #1c1917; flex: 1.4;\n\t\t}\n\t\t.thanks .end-actions button.primary:hover { background: #292524; border-color: #292524; }\n\t\t.thanks .end-actions button:focus-visible { outline: 2px solid #ea580c; outline-offset: 2px; }\n\t\t.thanks .end-hint {\n\t\t\tmargin: 11px 0 0; font-size: 11.5px; color: #a8a29e; text-align: center;\n\t\t}\n\t\t.thanks .end-sent {\n\t\t\tmargin-top: 16px; text-align: center; color: #57534e; font-size: 13.5px; line-height: 1.45;\n\t\t}\n\t\t@media (prefers-reduced-motion: reduce) {\n\t\t\t.thanks-card, .thanks .pay-edit { animation: none; }\n\t\t}\n\n\t\t@keyframes pulse {\n\t\t\t0% { box-shadow: 0 0 0 0 rgba(239,68,68,0.55); }\n\t\t\t70% { box-shadow: 0 0 0 10px rgba(239,68,68,0); }\n\t\t\t100% { box-shadow: 0 0 0 0 rgba(239,68,68,0); }\n\t\t}\n\t\t@media (prefers-reduced-motion: reduce) {\n\t\t\t.dot { animation: none; }\n\t\t\t.toast, .note-popover, .resume-toast { animation: none; }\n\t\t\t.resume-toast[data-leaving=\"true\"] { opacity: 0; }\n\t\t}\n\t`\n\tconst anchor = document.createElement('div')\n\tanchor.className = 'anchor'\n\n\tconst panel = document.createElement('div')\n\tpanel.className = 'panel'\n\tpanel.hidden = true\n\n\t// Toast slot: helper messages render here above the bar.\n\tconst toastSlot = document.createElement('div')\n\ttoastSlot.className = 'toast-slot'\n\n\t// Notes popover slot: rendered above the bar when open.\n\tconst notePopover = document.createElement('div')\n\tnotePopover.className = 'note-popover'\n\tnotePopover.hidden = true\n\n\tconst bar = document.createElement('div')\n\tbar.className = 'bar'\n\tbar.setAttribute('role', 'status')\n\tbar.setAttribute('aria-live', 'polite')\n\n\t// Mic chip = real button. Three states driven by data-mic-state.\n\tconst micBtn = document.createElement('button')\n\tmicBtn.type = 'button'\n\tmicBtn.className = 'mic'\n\tmicBtn.setAttribute('data-mic-state', 'recording')\n\tmicBtn.setAttribute('aria-pressed', 'false')\n\tmicBtn.setAttribute('aria-label', 'Mute microphone')\n\n\tconst dot = document.createElement('span')\n\tdot.className = 'dot'\n\tdot.setAttribute('data-state', store.indicatorState)\n\n\tconst micIcon = document.createElement('span')\n\tmicIcon.className = 'mic-icon'\n\tmicIcon.innerHTML = MIC_ICON_SVG\n\tmicIcon.setAttribute('aria-hidden', 'true')\n\n\tconst micLabel = document.createElement('span')\n\tmicLabel.className = 'mic-label'\n\tmicLabel.textContent = 'Recording'\n\n\tmicBtn.appendChild(dot)\n\tmicBtn.appendChild(micIcon)\n\tmicBtn.appendChild(micLabel)\n\tmicBtn.addEventListener('click', callbacks.onToggleMute)\n\tbar.appendChild(micBtn)\n\n\t// Notes button: icon-only by default, grows to show count once notes exist.\n\tconst noteBtn = document.createElement('button')\n\tnoteBtn.type = 'button'\n\tnoteBtn.className = 'note-btn'\n\tnoteBtn.setAttribute('aria-label', 'Add a timestamped note')\n\tnoteBtn.setAttribute('aria-expanded', 'false')\n\tnoteBtn.setAttribute('data-has-notes', 'false')\n\tnoteBtn.innerHTML = `<span class=\"note-icon\" aria-hidden=\"true\">${NOTE_ICON_SVG}</span><span class=\"note-count\" hidden></span>`\n\tnoteBtn.addEventListener('click', callbacks.onOpenNote)\n\tbar.appendChild(noteBtn)\n\n\tconst spacer = document.createElement('span')\n\tspacer.className = 'spacer'\n\tbar.appendChild(spacer)\n\n\tconst btn = document.createElement('button')\n\tbtn.type = 'button'\n\tbtn.className = 'btn finish-btn'\n\tbtn.textContent = 'Finish'\n\tbtn.addEventListener('click', callbacks.onFinish)\n\tbar.appendChild(btn)\n\n\tif (store.tasks.length > 0) installTasksToggle(bar, btn, store, callbacks.onToggleTasks)\n\n\tanchor.appendChild(panel)\n\tanchor.appendChild(toastSlot)\n\tanchor.appendChild(notePopover)\n\tanchor.appendChild(bar)\n\n\troot.appendChild(style)\n\troot.appendChild(anchor)\n\treturn root\n}\n\nexport function installTasksToggle(bar: HTMLElement, finishBtn: HTMLElement, store: RecorderStore, onToggleTasks: () => void): void {\n\tconst tasksBtn = document.createElement('button')\n\ttasksBtn.type = 'button'\n\ttasksBtn.className = 'btn tasks-btn'\n\ttasksBtn.textContent = `Tasks (${store.tasks.length})`\n\ttasksBtn.setAttribute('aria-expanded', store.tasksPanelOpen ? 'true' : 'false')\n\ttasksBtn.addEventListener('click', onToggleTasks)\n\tbar.insertBefore(tasksBtn, finishBtn)\n}\n\nexport function renderTasksPanel(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst panel = root.querySelector('.panel')\n\tif (!(panel instanceof HTMLElement)) return\n\t// Build content once.\n\tif (!panel.firstChild && store.tasks.length > 0) {\n\t\tconst ol = document.createElement('ol')\n\t\tfor (const task of store.tasks) {\n\t\t\tconst li = document.createElement('li')\n\t\t\tli.textContent = task.prompt\n\t\t\tol.appendChild(li)\n\t\t}\n\t\tpanel.appendChild(ol)\n\t}\n\tpanel.hidden = !store.tasksPanelOpen\n\tconst tasksBtn = root.querySelector('.tasks-btn')\n\tif (tasksBtn instanceof HTMLElement) {\n\t\ttasksBtn.setAttribute('aria-expanded', store.tasksPanelOpen ? 'true' : 'false')\n\t}\n}\n\nexport function readTasksPanelOpen(): boolean {\n\ttry { return window.sessionStorage?.getItem(TASKS_PANEL_OPEN_STORAGE_KEY) === '1' } catch { return false }\n}\nexport function writeTasksPanelOpen(open: boolean): void {\n\ttry { window.sessionStorage?.setItem(TASKS_PANEL_OPEN_STORAGE_KEY, open ? '1' : '0') } catch { /* ignore */ }\n}\n\nexport function micChipState(store: RecorderStore): 'recording' | 'muted' | 'none' | 'connecting' | 'silent' | 'inactive' {\n\tif (store.indicatorState === 'finishing' || store.indicatorState === 'done' || store.indicatorState === 'error') {\n\t\treturn 'inactive'\n\t}\n\tif (!store.hasMicPermission) {\n\t\t// Pending getUserMedia: show \"connecting\" so granted users never flash\n\t\t// the failure copy. Once startRecording resolves or rejects it clears\n\t\t// micAcquiring, and we fall through to the terminal \"none\" state.\n\t\tif (store.micAcquiring) return 'connecting'\n\t\treturn 'none'\n\t}\n\tif (store.muted) return 'muted'\n\t// Permission granted and not muted, but the live track is reading digital\n\t// silence (dead mic or a virtual silent input device). Warn, non-blocking:\n\t// recording continues, this just prompts the participant to check their mic.\n\tif (store.micSilent) return 'silent'\n\treturn 'recording'\n}\n\nexport function renderIndicatorState(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst dot = root.querySelector('.dot')\n\tconst mic = root.querySelector<HTMLButtonElement>('.mic')\n\tconst micIcon = root.querySelector('.mic-icon')\n\tconst micLabel = root.querySelector('.mic-label')\n\tconst btn = root.querySelector<HTMLButtonElement>('.finish-btn')\n\tif (!(dot instanceof HTMLElement) || !mic || !(micIcon instanceof HTMLElement) || !(micLabel instanceof HTMLElement) || !btn) return\n\n\tdot.setAttribute('data-state', store.indicatorState)\n\tconst chipState = micChipState(store)\n\t// The silent-mic warning reuses the existing \"none\" warning treatment\n\t// (muted-grey, tappable retry affordance) rather than inventing a new visual\n\t// — same as the \"Mic blocked, tap to retry\" failed state, just different copy.\n\tconst micStateAttr = chipState === 'inactive' || chipState === 'silent' ? 'none' : chipState\n\tmic.setAttribute('data-mic-state', micStateAttr)\n\t// Distinguish \"acquiring\" (genuinely failed, actionable) from \"connecting\"\n\t// at the attribute level so the dot/visuals key off the right state. The\n\t// failed terminal chip is a retry affordance; mark it so CSS can style it.\n\tmic.removeAttribute('data-mic-fail')\n\tif (chipState === 'none') mic.setAttribute('data-mic-fail', store.micFailReason ?? 'blocked')\n\n\t// Finish-button copy is driven by the indicatorState (network / lifecycle).\n\tswitch (store.indicatorState) {\n\t\tcase 'recording':\n\t\tcase 'no-audio':\n\t\t\tbtn.textContent = 'Finish'\n\t\t\tbtn.disabled = false\n\t\t\tbreak\n\t\tcase 'finishing':\n\t\t\tbtn.textContent = 'Saving'\n\t\t\tbtn.disabled = true\n\t\t\tbreak\n\t\tcase 'done':\n\t\t\tbtn.textContent = 'Done'\n\t\t\tbtn.disabled = true\n\t\t\tbreak\n\t\tcase 'error':\n\t\t\tbtn.textContent = 'Retry'\n\t\t\tbtn.disabled = false\n\t\t\tbreak\n\t}\n\n\t// Mic chip copy + icon. Replay continues in all states; the chip only\n\t// describes the audio track.\n\tswitch (chipState) {\n\t\tcase 'recording':\n\t\t\tmicIcon.innerHTML = MIC_ICON_SVG\n\t\t\tmicLabel.textContent = 'Recording'\n\t\t\tmic.setAttribute('aria-label', 'Mute microphone')\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\tcase 'muted':\n\t\t\tmicIcon.innerHTML = MIC_MUTED_ICON_SVG\n\t\t\tmicLabel.textContent = 'Muted'\n\t\t\tmic.setAttribute('aria-label', 'Unmute microphone')\n\t\t\tmic.setAttribute('aria-pressed', 'true')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\tcase 'connecting':\n\t\t\t// getUserMedia still pending. Granted users sit here briefly instead\n\t\t\t// of flashing the failure copy. Not yet a toggle, so unfocusable.\n\t\t\tmicIcon.innerHTML = MIC_ICON_SVG\n\t\t\tmicLabel.textContent = 'Connecting mic'\n\t\t\tmic.setAttribute('aria-label', 'Connecting microphone')\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.setAttribute('tabindex', '-1')\n\t\t\tbreak\n\t\tcase 'silent':\n\t\t\t// Permission granted, recording live, but the input is digital\n\t\t\t// silence (dead mic or a virtual silent device). Warn, non-blocking:\n\t\t\t// recording continues. Tappable so the participant can re-acquire the\n\t\t\t// mic after switching their input device. Auto-clears when real audio\n\t\t\t// returns (the monitor flips store.micSilent back to false).\n\t\t\tmicIcon.innerHTML = MIC_MUTED_ICON_SVG\n\t\t\tmicLabel.textContent = \"We can't hear you, tap to recheck\"\n\t\t\tmic.setAttribute('aria-label', \"We can't hear your microphone. Check your input device, then tap to recheck. Recording continues.\")\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\tcase 'none': {\n\t\t\t// Genuinely failed terminal state. Actionable: the chip is a button\n\t\t\t// that re-attempts mic acquisition. Keyboard-focusable (no tabindex\n\t\t\t// -1). Replay keeps recording regardless.\n\t\t\tmicIcon.innerHTML = MIC_MUTED_ICON_SVG\n\t\t\tconst failLabel =\n\t\t\t\tstore.micFailReason === 'not-found' ? 'No mic found, tap to retry' :\n\t\t\t\t'Mic blocked, tap to retry'\n\t\t\tconst failAria =\n\t\t\t\tstore.micFailReason === 'not-found'\n\t\t\t\t\t? 'No microphone found, tap to retry. Replay continues.'\n\t\t\t\t\t: 'Microphone blocked, tap to retry. Replay continues.'\n\t\t\tmicLabel.textContent = failLabel\n\t\t\tmic.setAttribute('aria-label', failAria)\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.removeAttribute('tabindex')\n\t\t\tbreak\n\t\t}\n\t\tcase 'inactive':\n\t\t\tmicIcon.innerHTML = MIC_ICON_SVG\n\t\t\tmicLabel.textContent =\n\t\t\t\tstore.indicatorState === 'finishing' ? 'Saving' :\n\t\t\t\tstore.indicatorState === 'done' ? 'Saved' :\n\t\t\t\t'Save failed'\n\t\t\tmic.setAttribute('aria-label', 'Recording stopped')\n\t\t\tmic.setAttribute('aria-pressed', 'false')\n\t\t\tmic.setAttribute('tabindex', '-1')\n\t\t\tbreak\n\t}\n}\n\nexport function renderNotesCount(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst noteBtn = root.querySelector('.note-btn')\n\tconst count = root.querySelector('.note-count')\n\tif (!(noteBtn instanceof HTMLElement) || !(count instanceof HTMLElement)) return\n\tconst n = store.notes.length\n\tnoteBtn.setAttribute('data-has-notes', n > 0 ? 'true' : 'false')\n\tif (n > 0) {\n\t\tcount.textContent = String(n)\n\t\tcount.hidden = false\n\t\tnoteBtn.setAttribute('aria-label', `Add a timestamped note (${n} so far)`)\n\t} else {\n\t\tcount.textContent = ''\n\t\tcount.hidden = true\n\t\tnoteBtn.setAttribute('aria-label', 'Add a timestamped note')\n\t}\n}\n\nexport function showMuteToast(store: RecorderStore): void {\n\tif (store.muteToastShown) return\n\tstore.muteToastShown = true\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst slot = root.querySelector('.toast-slot')\n\tif (!(slot instanceof HTMLElement)) return\n\tslot.innerHTML = ''\n\tconst toast = document.createElement('div')\n\ttoast.className = 'toast'\n\ttoast.setAttribute('role', 'status')\n\ttoast.innerHTML = `<strong>Mic off.</strong> Screen is still recording. Tap to unmute.`\n\tslot.appendChild(toast)\n\tconst outer = window.setTimeout(() => {\n\t\tif (!toast.isConnected) return\n\t\ttoast.setAttribute('data-leaving', 'true')\n\t\tconst inner = window.setTimeout(() => {\n\t\t\tif (toast.isConnected) toast.remove()\n\t\t}, 260)\n\t\tstore.muteToastTimers.push(inner)\n\t}, 3000)\n\tstore.muteToastTimers.push(outer)\n}\n\n// Brief, unobtrusive confirmation that recording picked back up after the\n// participant returned from a hard navigation (e.g. an OAuth round-trip). It\n// reuses the toast slot above the bar and the shared toast-in/out animations,\n// but with the live-record red accent so it reassures rather than warns. Shows\n// once, then auto-dismisses; clears store.resumed so a later render can't\n// re-fire it. Reduced-motion is handled in CSS (no slide, instant fade).\nexport function showResumedToast(store: RecorderStore): void {\n\tif (!store.resumed) return\n\tstore.resumed = false\n\t// Only show the reassuring \"Recording resumed\" pill when the mic genuinely\n\t// came back live. If getUserMedia rejected on resume (blocked / no device /\n\t// unsupported -> hasMicPermission false, indicatorState 'no-audio'), a green\n\t// pill claiming we're recording would be a lie while audio is dead. Bail and\n\t// let the existing mic-blocked affordance (the mic chip in its failed state)\n\t// carry the message instead. Same signal the mic chip gates on (line ~1119).\n\tif (!store.hasMicPermission || store.indicatorState === 'no-audio') return\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst slot = root.querySelector('.toast-slot')\n\tif (!(slot instanceof HTMLElement)) return\n\tslot.innerHTML = ''\n\tconst toast = document.createElement('div')\n\ttoast.className = 'resume-toast'\n\ttoast.setAttribute('role', 'status')\n\tconst dot = document.createElement('span')\n\tdot.className = 'dot'\n\tdot.setAttribute('aria-hidden', 'true')\n\tconst label = document.createElement('span')\n\tlabel.textContent = 'Recording resumed'\n\ttoast.appendChild(dot)\n\ttoast.appendChild(label)\n\tslot.appendChild(toast)\n\tconst outer = window.setTimeout(() => {\n\t\tif (!toast.isConnected) return\n\t\ttoast.setAttribute('data-leaving', 'true')\n\t\tconst inner = window.setTimeout(() => {\n\t\t\tif (toast.isConnected) toast.remove()\n\t\t}, 260)\n\t\tstore.resumeToastTimers.push(inner)\n\t}, 3200)\n\tstore.resumeToastTimers.push(outer)\n}\n\nexport function openNotePopover(store: RecorderStore, onSave: (text: string) => void, onCancel: () => void): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst pop = root.querySelector('.note-popover')\n\tconst noteBtn = root.querySelector('.note-btn')\n\tif (!(pop instanceof HTMLElement) || !(noteBtn instanceof HTMLElement)) return\n\n\tstore.notesPopoverOpen = true\n\tstore.notePopoverAtMs = Date.now() - store.startedAt\n\tnoteBtn.setAttribute('aria-expanded', 'true')\n\n\tpop.innerHTML = ''\n\tconst head = document.createElement('div')\n\thead.className = 'note-head'\n\thead.innerHTML = `<span>Add a note</span>`\n\n\tconst form = document.createElement('form')\n\tform.style.cssText = 'display:flex;flex-direction:column;gap:10px;margin:0;'\n\tform.noValidate = true\n\n\tconst ta = document.createElement('textarea')\n\tta.className = 'note-textarea'\n\tta.placeholder = 'What just happened? Confusing? Surprising? Broken?'\n\tta.rows = 3\n\tta.setAttribute('aria-label', 'Note text')\n\n\tconst actions = document.createElement('div')\n\tactions.className = 'note-actions'\n\tconst hint = document.createElement('span')\n\thint.className = 'hint'\n\thint.innerHTML = '<kbd style=\"font-family:inherit\">Cmd</kbd>+Enter to save'\n\tconst group = document.createElement('div')\n\tgroup.className = 'group'\n\tconst cancelBtn = document.createElement('button')\n\tcancelBtn.type = 'button'\n\tcancelBtn.className = 'btn btn-ghost'\n\tcancelBtn.textContent = 'Cancel'\n\tconst saveBtn = document.createElement('button')\n\tsaveBtn.type = 'submit'\n\tsaveBtn.className = 'btn btn-primary'\n\tsaveBtn.textContent = 'Save'\n\tgroup.appendChild(cancelBtn)\n\tgroup.appendChild(saveBtn)\n\tactions.appendChild(hint)\n\tactions.appendChild(group)\n\n\tform.appendChild(ta)\n\tform.appendChild(actions)\n\n\tpop.appendChild(head)\n\tpop.appendChild(form)\n\tpop.hidden = false\n\n\tconst submit = (): void => {\n\t\tconst text = ta.value.trim()\n\t\tif (!text) { onCancel(); return }\n\t\tonSave(text)\n\t}\n\tform.addEventListener('submit', e => { e.preventDefault(); submit() })\n\tcancelBtn.addEventListener('click', () => onCancel())\n\tta.addEventListener('keydown', e => {\n\t\tif ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n\t\t\te.preventDefault()\n\t\t\tsubmit()\n\t\t} else if (e.key === 'Escape') {\n\t\t\te.preventDefault()\n\t\t\tonCancel()\n\t\t}\n\t})\n\n\t// Autofocus on next frame so animation can finish without scroll jank.\n\twindow.requestAnimationFrame(() => { ta.focus({ preventScroll: true }) })\n}\n\nexport function closeNotePopover(store: RecorderStore): void {\n\tconst root = store.indicatorRoot\n\tif (!root) return\n\tconst pop = root.querySelector('.note-popover')\n\tconst noteBtn = root.querySelector('.note-btn')\n\tif (pop instanceof HTMLElement) {\n\t\tpop.hidden = true\n\t\tpop.innerHTML = ''\n\t}\n\tif (noteBtn instanceof HTMLElement) noteBtn.setAttribute('aria-expanded', 'false')\n\tstore.notesPopoverOpen = false\n\tstore.notePopoverAtMs = null\n}\n\n// Escape user-controlled strings before they touch innerHTML. The payout email\n// comes from our own DB, but it originated as participant input, so treat it as\n// untrusted and never interpolate it raw into markup.\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, '&')\n\t\t.replace(/</g, '<')\n\t\t.replace(/>/g, '>')\n\t\t.replace(/\"/g, '"')\n\t\t.replace(/'/g, ''')\n}\n\nfunction isValidEmail(value: string): boolean {\n\t// Pragmatic check; the server re-validates with zod .email().\n\treturn /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)\n}\n\nexport function showThanksScreen(root: ShadowRoot, opts: ThanksOptions): void {\n\tconst overlay = document.createElement('div')\n\toverlay.className = 'thanks'\n\toverlay.setAttribute('role', 'dialog')\n\toverlay.setAttribute('aria-modal', 'true')\n\n\tconst card = document.createElement('div')\n\tcard.className = 'thanks-card'\n\toverlay.appendChild(card)\n\troot.appendChild(overlay)\n\n\t// Ended-early branch: warmer, non-punishing, keep Resume primary.\n\tif (opts.payment && !opts.payment.qualified) {\n\t\trenderEndedEarly(card, opts)\n\t\treturn\n\t}\n\n\t// Complete branch (also the fallback when payment is null: a clean \"saved\"\n\t// confirmation with the wrap-up note, no payout block since we have no data).\n\trenderComplete(card, opts)\n}\n\n// Terminal notice shown when a resume attempt finds the session already closed\n// (server returned 409/410 on adopt, e.g. the participant took too long on a\n// third-party sign-in and the stale sweep finalised it). Reuses the ended-early\n// screen's overlay + card so it matches the recorder's visual language exactly,\n// but renders a single calm, honest message with no actions: the test is over,\n// there is nothing to resume. Copy does not over-promise: it says earlier\n// responses were saved, which is true because the session was finalised\n// server-side, so any recording already uploaded is intact.\nexport function showSessionEndedScreen(root: ShadowRoot): void {\n\t// Don't stack a second overlay if a thanks/ended screen is already up.\n\tif (root.querySelector('.thanks')) return\n\n\tconst overlay = document.createElement('div')\n\toverlay.className = 'thanks'\n\toverlay.setAttribute('role', 'dialog')\n\toverlay.setAttribute('aria-modal', 'true')\n\n\tconst card = document.createElement('div')\n\tcard.className = 'thanks-card'\n\tconst head = document.createElement('div')\n\thead.className = 'head'\n\thead.innerHTML = `\n\t\t<div class=\"check ended\" aria-hidden=\"true\">${FLAG_ICON_SVG}</div>\n\t\t<h2>This test session ended</h2>\n\t\t<p class=\"lede\">Thanks for taking part. Your earlier responses were saved. You can close this tab.</p>\n\t`\n\tcard.appendChild(head)\n\toverlay.appendChild(card)\n\troot.appendChild(overlay)\n}\n\n// Builds the verified-checks list. `done` rows get the green tick; an unfinished\n// tasks row (ended-early) gets the hollow todo dot.\nfunction checksList(rows: Array<{ label: string; done: boolean; muted?: boolean }>): string {\n\tconst items = rows\n\t\t.map(r => {\n\t\t\tconst icClass = r.done ? 'ic done' : 'ic todo'\n\t\t\tconst icon = r.done ? TICK_SM_SVG : ''\n\t\t\tconst liClass = r.muted ? ' class=\"muted-row\"' : ''\n\t\t\treturn `<li${liClass}><span class=\"${icClass}\" aria-hidden=\"true\">${icon}</span><span>${escapeHtml(r.label)}</span></li>`\n\t\t})\n\t\t.join('')\n\treturn `<ul class=\"checks\">${items}</ul>`\n}\n\nfunction renderComplete(card: HTMLElement, opts: ThanksOptions): void {\n\tconst payment = opts.payment\n\tconst reward = payment?.reward ?? null\n\tconst defaultEmail = payment?.payoutEmail ?? null\n\tconst tasksTotal = payment?.tasksTotal ?? 0\n\n\tconst head = document.createElement('div')\n\thead.className = 'head'\n\tconst lede = reward\n\t\t? `We have your recording. Confirm where to send your ${escapeHtml(reward)} and the team will review it shortly.`\n\t\t: 'We have your recording. Thanks for taking the time to walk us through it.'\n\thead.innerHTML = `\n\t\t<div class=\"check ok\" aria-hidden=\"true\">${TICK_ICON_SVG}</div>\n\t\t<h2>You're done.</h2>\n\t\t<p class=\"lede\">${lede}</p>\n\t\t${tasksTotal > 0\n\t\t\t? checksList([\n\t\t\t\t\t{ label: tasksTotal === 1 ? '1 task completed' : `All ${tasksTotal} tasks completed`, done: true },\n\t\t\t\t\t{ label: 'Voice recording captured', done: true },\n\t\t\t\t\t{ label: 'Screen replay uploaded', done: true },\n\t\t\t\t])\n\t\t\t: checksList([\n\t\t\t\t\t{ label: 'Voice recording captured', done: true },\n\t\t\t\t\t{ label: 'Screen replay uploaded', done: true },\n\t\t\t\t])}\n\t`\n\tcard.appendChild(head)\n\n\t// If we have no payment data (older server) skip payout entirely and go\n\t// straight to the wrap-up note.\n\tif (!payment) {\n\t\tappendNoteSection(card, opts, 'Your session was saved. Anything you would add?')\n\t\treturn\n\t}\n\n\trenderPayout(card, opts, reward, defaultEmail)\n}\n\n// Payout capture: one-tap default to the sign-up email, with a quieter expander\n// to use a different email. Progressive disclosure (the default path is not a form).\nfunction renderPayout(card: HTMLElement, opts: ThanksOptions, reward: string | null, defaultEmail: string | null): void {\n\tconst wrap = document.createElement('div')\n\twrap.className = 'payout'\n\n\tconst rewardLabel = reward ?? 'my reward'\n\tconst haveDefault = !!defaultEmail && isValidEmail(defaultEmail)\n\n\twrap.innerHTML = `\n\t\t<p class=\"payout-q\">Where should we send ${escapeHtml(reward ?? 'your reward')}?</p>\n\t\t<button type=\"button\" class=\"pay-primary\" ${haveDefault ? '' : 'hidden'}>\n\t\t\tSend <span class=\"amt\">${escapeHtml(rewardLabel)}</span>${haveDefault ? ` to ${escapeHtml(defaultEmail as string)}` : ''}\n\t\t</button>\n\t\t<button type=\"button\" class=\"pay-alt\">${haveDefault ? 'Use a different email' : 'Add your payout email'}</button>\n\t\t<div class=\"pay-edit\" ${haveDefault ? 'hidden' : ''}>\n\t\t\t<label class=\"pay-label\" for=\"usero-payout-email\">Payout email</label>\n\t\t\t<input id=\"usero-payout-email\" class=\"pay-input\" type=\"email\" inputmode=\"email\"\n\t\t\t\tautocomplete=\"email\" placeholder=\"you@example.com\" value=\"${haveDefault ? '' : escapeHtml(defaultEmail ?? '')}\" />\n\t\t</div>\n\t\t<p class=\"pay-eta\">Reward arrives within about 2 days of the team reviewing it.</p>\n\t`\n\tcard.appendChild(wrap)\n\n\tconst primary = wrap.querySelector<HTMLButtonElement>('.pay-primary')\n\tconst altLink = wrap.querySelector<HTMLButtonElement>('.pay-alt')\n\tconst editBox = wrap.querySelector<HTMLElement>('.pay-edit')\n\tconst emailInput = wrap.querySelector<HTMLInputElement>('.pay-input')\n\tif (!primary || !altLink || !editBox || !emailInput) return\n\n\tconst confirm = async (destination: string | null): Promise<void> => {\n\t\tprimary.disabled = true\n\t\taltLink.style.pointerEvents = 'none'\n\t\tconst ok = await opts.onPayout(destination)\n\t\t// Whatever the network outcome, the session is payable (server defaults to\n\t\t// the sign-up email). Move the participant forward rather than trapping them.\n\t\twrap.remove()\n\t\tconst confirmedTo = destination ?? defaultEmail\n\t\tconst sentMsg = confirmedTo\n\t\t\t? `${reward ? `${reward} is` : \"Your reward is\"} set to go to ${confirmedTo}.`\n\t\t\t: 'Your reward is on its way.'\n\t\tconst note = ok ? sentMsg : `${sentMsg} (We will retry sending the details.)`\n\t\tappendNoteSection(card, opts, `${note} Anything you would add before you go?`)\n\t}\n\n\t// One-tap default path.\n\tprimary.addEventListener('click', () => { void confirm(null) })\n\n\t// Expander: reveal the email field, focus it, submit on Enter.\n\tconst openEditor = (): void => {\n\t\tprimary.hidden = true\n\t\taltLink.hidden = true\n\t\teditBox.hidden = false\n\t\t// Append a confirm button under the input on first open.\n\t\tif (!editBox.querySelector('.pay-confirm')) {\n\t\t\tconst btn = document.createElement('button')\n\t\t\tbtn.type = 'button'\n\t\t\tbtn.className = 'pay-primary pay-confirm'\n\t\t\tbtn.style.marginTop = '12px'\n\t\t\tbtn.textContent = reward ? `Send ${reward} here` : 'Use this email'\n\t\t\teditBox.appendChild(btn)\n\t\t\tbtn.addEventListener('click', () => void submitEmail())\n\t\t}\n\t\twindow.requestAnimationFrame(() => emailInput.focus({ preventScroll: true }))\n\t}\n\n\tconst submitEmail = async (): Promise<void> => {\n\t\tconst value = emailInput.value.trim().toLowerCase()\n\t\tif (!isValidEmail(value)) {\n\t\t\temailInput.focus()\n\t\t\temailInput.style.borderColor = '#dc2626'\n\t\t\treturn\n\t\t}\n\t\tawait confirm(value)\n\t}\n\n\taltLink.addEventListener('click', openEditor)\n\temailInput.addEventListener('input', () => { emailInput.style.borderColor = '' })\n\temailInput.addEventListener('keydown', e => {\n\t\tif (e.key === 'Enter') { e.preventDefault(); void submitEmail() }\n\t})\n}\n\nfunction renderEndedEarly(card: HTMLElement, opts: ThanksOptions): void {\n\tconst payment = opts.payment\n\tconst done = payment?.tasksDone ?? 0\n\tconst total = payment?.tasksTotal ?? 0\n\tconst reward = payment?.reward ?? null\n\n\tconst head = document.createElement('div')\n\thead.className = 'head'\n\tconst lede = total > 0\n\t\t? `We saw ${done} of ${total} ${total === 1 ? 'task' : 'tasks'} finished. No worries, you can pick up right where you left off.`\n\t\t: 'It looks like the session ended before you finished. No worries, you can pick up where you left off.'\n\thead.innerHTML = `\n\t\t<div class=\"check early\" aria-hidden=\"true\">${CLOCK_ICON_SVG}</div>\n\t\t<h2>Looks like you stopped early</h2>\n\t\t<p class=\"lede\">${lede}</p>\n\t`\n\tcard.appendChild(head)\n\n\t// Per-task progress when we know the counts: done rows ticked, the rest hollow.\n\tif (total > 0) {\n\t\tconst rows: Array<{ label: string; done: boolean }> = []\n\t\tfor (let i = 0; i < total; i += 1) {\n\t\t\trows.push({ label: `Task ${i + 1}`, done: i < done })\n\t\t}\n\t\tconst list = document.createElement('div')\n\t\tlist.innerHTML = checksList(rows)\n\t\tconst ul = list.firstElementChild\n\t\tif (ul) card.appendChild(ul)\n\t}\n\n\tconst note = document.createElement('div')\n\tnote.className = 'early-note'\n\tnote.innerHTML = `${SPARK_ICON_SVG}<span><strong style=\"font-weight:600\">Resume the test.</strong> ${\n\t\treward ? `Your ${escapeHtml(reward)} reward unlocks` : 'The reward unlocks'\n\t} once all ${total > 0 ? total : 'the'} ${total === 1 ? 'task is' : 'tasks are'} done.</span>`\n\tcard.appendChild(note)\n\n\tconst actions = document.createElement('div')\n\tactions.className = 'early-actions'\n\tconst resume = document.createElement('button')\n\tresume.type = 'button'\n\tresume.className = 'resume-btn'\n\tresume.textContent = 'Resume where I left off'\n\tconst exit = document.createElement('button')\n\texit.type = 'button'\n\texit.className = 'exit-btn'\n\texit.textContent = \"Thanks for trying. No reward this time since the tasks weren't finished.\"\n\tactions.appendChild(resume)\n\tactions.appendChild(exit)\n\tcard.appendChild(actions)\n\n\tresume.addEventListener('click', () => {\n\t\tconst overlay = card.closest('.thanks')\n\t\tif (overlay instanceof HTMLElement) overlay.remove()\n\t\topts.onResume()\n\t})\n\texit.addEventListener('click', () => {\n\t\tcard.innerHTML = ''\n\t\tconst sent = document.createElement('p')\n\t\tsent.className = 'end-sent'\n\t\tsent.textContent = 'Thanks for giving it a go. You can close this tab now.'\n\t\tcard.appendChild(sent)\n\t})\n}\n\n// The wrap-up note section, shared by the complete path (after payout) and the\n// older-server fallback. Mirrors the prior behaviour: Cmd/Ctrl+Enter to send,\n// retry on failure, skip allowed.\nfunction appendNoteSection(card: HTMLElement, opts: ThanksOptions, prompt: string): void {\n\tconst section = document.createElement('div')\n\tsection.className = 'note-section'\n\n\tconst form = document.createElement('form')\n\tform.noValidate = true\n\tform.innerHTML = `\n\t\t<label class=\"end-label\" for=\"usero-end-note\">${escapeHtml(prompt)}</label>\n\t\t<textarea\n\t\t\tid=\"usero-end-note\"\n\t\t\tclass=\"end-textarea\"\n\t\t\trows=\"3\"\n\t\t\tplaceholder=\"Confusing bits, things you liked, what you'd change...\"\n\t\t></textarea>\n\t\t<div class=\"end-actions\">\n\t\t\t<button type=\"button\" class=\"skip\">Skip</button>\n\t\t\t<button type=\"submit\" class=\"primary\">Send feedback</button>\n\t\t</div>\n\t\t<p class=\"end-hint\">Cmd or Ctrl plus Enter to send. Either button is fine.</p>\n\t`\n\tsection.appendChild(form)\n\tcard.appendChild(section)\n\n\tconst ta = form.querySelector<HTMLTextAreaElement>('#usero-end-note')\n\tconst skipBtn = form.querySelector<HTMLButtonElement>('button.skip')\n\tif (!ta || !skipBtn) return\n\n\tconst swapToSent = (message: string): void => {\n\t\tsection.remove()\n\t\tconst sent = document.createElement('p')\n\t\tsent.className = 'end-sent'\n\t\tsent.textContent = message\n\t\tcard.appendChild(sent)\n\t}\n\n\tconst ERROR_CLASS = 'end-error'\n\tconst showError = (message: string): void => {\n\t\tconst prior = form.querySelector(`.${ERROR_CLASS}`)\n\t\tif (prior) prior.remove()\n\t\tconst err = document.createElement('p')\n\t\terr.className = ERROR_CLASS\n\t\terr.textContent = message\n\t\terr.setAttribute('role', 'alert')\n\t\terr.style.cssText = 'margin:10px 0 0;font-size:12.5px;color:#b91c1c;text-align:center;'\n\t\tform.appendChild(err)\n\t}\n\n\tconst submit = async (): Promise<void> => {\n\t\tconst text = ta.value.trim()\n\t\tta.disabled = true\n\t\tskipBtn.disabled = true\n\t\tconst submitBtn = form.querySelector<HTMLButtonElement>('button.primary')\n\t\tif (submitBtn) submitBtn.disabled = true\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tawait Promise.race([\n\t\t\t\t\tPromise.resolve(opts.onSubmitNote(text)),\n\t\t\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\t\t\twindow.setTimeout(() => reject(new Error('timeout')), 30000)\n\t\t\t\t\t}),\n\t\t\t\t])\n\t\t\t\tswapToSent('Thanks. You can close this tab.')\n\t\t\t} catch {\n\t\t\t\tta.disabled = false\n\t\t\t\tskipBtn.disabled = false\n\t\t\t\tif (submitBtn) submitBtn.disabled = false\n\t\t\t\tshowError(\"Couldn't save your note. Try again?\")\n\t\t\t}\n\t\t} else {\n\t\t\topts.onSkip()\n\t\t\tswapToSent('All set. You can close this tab.')\n\t\t}\n\t}\n\n\tform.addEventListener('submit', e => { e.preventDefault(); void submit() })\n\tskipBtn.addEventListener('click', () => { ta.value = ''; void submit() })\n\tta.addEventListener('keydown', e => {\n\t\tif ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n\t\t\te.preventDefault()\n\t\t\tvoid submit()\n\t\t}\n\t})\n\n\twindow.requestAnimationFrame(() => { ta.focus({ preventScroll: true }) })\n}\n","// Recording engine for the user-test plugin: getUserMedia / MediaRecorder\n// lifecycle, the chunk upload queue with retry + IndexedDB offline stash, the\n// silent-microphone guard, and mute/stop handling. It writes the resume pointer\n// (via session.persistActiveSession) as the chunk index advances, and pokes the\n// indicator (via ui.renderIndicatorState) on mic state transitions.\n\nimport type { PluginContext } from '../../plugin'\nimport { persistActiveSession } from './session'\nimport {\n\ttype ChunkUploadOutcome,\n\tIDB_NAME,\n\tIDB_STORE,\n\ttype PendingChunk,\n\ttype RecorderStore,\n\tSILENCE_FLOOR_DB,\n\tSILENCE_POLL_MS,\n\tSILENCE_RMS_DB_THRESHOLD,\n\tSILENCE_SUSTAINED_MS,\n\ttype SilenceMonitor,\n} from './shared'\nimport { renderIndicatorState } from './ui'\n\nexport function isMediaRecorderSupported(): boolean {\n\treturn typeof window !== 'undefined' && typeof window.MediaRecorder !== 'undefined' && typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getUserMedia\n}\n\nexport function pickMimeType(): string | undefined {\n\tconst candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus', 'audio/mp4']\n\tfor (const candidate of candidates) {\n\t\tif (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported?.(candidate)) {\n\t\t\treturn candidate\n\t\t}\n\t}\n\treturn undefined\n}\n\n// IndexedDB helpers. Best-effort, never throw upstream — if IDB is missing\n// or quota-blown we just lose offline resilience but the live upload path\n// still runs.\nfunction idbOpen(): Promise<IDBDatabase | null> {\n\treturn new Promise(resolve => {\n\t\tif (typeof indexedDB === 'undefined') {\n\t\t\tresolve(null)\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tconst req = indexedDB.open(IDB_NAME, 1)\n\t\t\treq.onupgradeneeded = (): void => {\n\t\t\t\tconst db = req.result\n\t\t\t\tif (!db.objectStoreNames.contains(IDB_STORE)) {\n\t\t\t\t\tdb.createObjectStore(IDB_STORE, { keyPath: 'id' })\n\t\t\t\t}\n\t\t\t}\n\t\t\treq.onsuccess = (): void => resolve(req.result)\n\t\t\treq.onerror = (): void => resolve(null)\n\t\t} catch {\n\t\t\tresolve(null)\n\t\t}\n\t})\n}\n\nasync function idbStashChunk(chunk: PendingChunk): Promise<void> {\n\tconst db = await idbOpen()\n\tif (!db) return\n\tawait new Promise<void>(resolve => {\n\t\ttry {\n\t\t\tconst tx = db.transaction(IDB_STORE, 'readwrite')\n\t\t\ttx.objectStore(IDB_STORE).put(chunk)\n\t\t\ttx.oncomplete = (): void => resolve()\n\t\t\ttx.onerror = (): void => resolve()\n\t\t\ttx.onabort = (): void => resolve()\n\t\t} catch {\n\t\t\tresolve()\n\t\t}\n\t})\n\tdb.close()\n}\n\nasync function idbDeleteChunk(id: string): Promise<void> {\n\tconst db = await idbOpen()\n\tif (!db) return\n\tawait new Promise<void>(resolve => {\n\t\ttry {\n\t\t\tconst tx = db.transaction(IDB_STORE, 'readwrite')\n\t\t\ttx.objectStore(IDB_STORE).delete(id)\n\t\t\ttx.oncomplete = (): void => resolve()\n\t\t\ttx.onerror = (): void => resolve()\n\t\t\ttx.onabort = (): void => resolve()\n\t\t} catch {\n\t\t\tresolve()\n\t\t}\n\t})\n\tdb.close()\n}\n\nasync function idbListChunks(sessionId: string): Promise<PendingChunk[]> {\n\tconst db = await idbOpen()\n\tif (!db) return []\n\tconst items = await new Promise<PendingChunk[]>(resolve => {\n\t\ttry {\n\t\t\tconst tx = db.transaction(IDB_STORE, 'readonly')\n\t\t\tconst req = tx.objectStore(IDB_STORE).getAll()\n\t\t\treq.onsuccess = (): void => {\n\t\t\t\tconst all = (req.result as PendingChunk[]) ?? []\n\t\t\t\tresolve(all.filter(c => c.sessionId === sessionId))\n\t\t\t}\n\t\t\treq.onerror = (): void => resolve([])\n\t\t} catch {\n\t\t\tresolve([])\n\t\t}\n\t})\n\tdb.close()\n\treturn items\n}\n\n// Classification of a single chunk-upload HTTP response. A pure function of the\n// status + parsed body so it can be unit-tested without a real fetch:\n// - 'ok': 2xx. The chunk landed (or the server idempotently accepted a re-fire).\n// - 'closed': the server says the session is finalised/closed and resume must\n// stop. The contract is a 409 carrying `{ closeResume: true }` (the chunk\n// route returns exactly this when the session status is 'finalising'; see\n// decideChunkUpload on the server). This is the ONLY signal that stops\n// recording. A 409 WITHOUT closeResume (e.g. completed/failed past the\n// late-chunk grace) is treated as a plain rejection, NOT a stop, so a racing\n// trailing chunk can't tear down a session the participant may still resume.\n// - 'retry': transient (network handled by the caller's catch, or 5xx / 408 /\n// 429). Worth another attempt.\n// - 'fatal': a non-closing 4xx (bad request, 413 too large, 404). Won't improve\n// on retry; bail this chunk. The caller maps both 'fatal' and exhausted\n// 'retry' to the 'failed' outcome (stash for a later offline flush).\nexport type ChunkResponseClass = 'ok' | 'closed' | 'retry' | 'fatal'\n\nexport function classifyChunkResponse(status: number, body: unknown): ChunkResponseClass {\n\tif (status >= 200 && status < 300) return 'ok'\n\t// Definitive session-closed signal: 409 + closeResume: true. Read the flag\n\t// defensively (the body may be absent / non-object on a malformed response).\n\tif (status === 409 && typeof body === 'object' && body !== null && (body as { closeResume?: unknown }).closeResume === true) {\n\t\treturn 'closed'\n\t}\n\t// 5xx and the two retry-after statuses are transient.\n\tif (status >= 500 || status === 408 || status === 429) return 'retry'\n\t// Any other 4xx is a hard rejection that retries can't fix.\n\tif (status >= 400) return 'fatal'\n\t// 1xx/3xx shouldn't reach here for this endpoint; treat as transient.\n\treturn 'retry'\n}\n\nasync function uploadChunkWithRetry(\n\tapiUrl: string,\n\tsessionId: string,\n\tindex: number,\n\tblob: Blob,\n\tlogger: PluginContext['logger'],\n\tmaxAttempts = 5,\n): Promise<ChunkUploadOutcome> {\n\tconst url = `${apiUrl.replace(/\\/$/, '')}/api/user-test-sessions/${encodeURIComponent(sessionId)}/chunk?index=${index}`\n\tlet attempt = 0\n\twhile (attempt < maxAttempts) {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbody: blob,\n\t\t\t\theaders: { 'Content-Type': blob.type || 'audio/webm' },\n\t\t\t\tkeepalive: blob.size <= 60 * 1024, // browsers cap keepalive bodies\n\t\t\t})\n\t\t\tif (res.ok) return 'ok'\n\t\t\t// Read the body to distinguish a session-closed signal (409 +\n\t\t\t// closeResume) from an ordinary rejection. Tolerate a non-JSON body.\n\t\t\tlet body: unknown = null\n\t\t\ttry {\n\t\t\t\tbody = await res.json()\n\t\t\t} catch {\n\t\t\t\tbody = null\n\t\t\t}\n\t\t\tconst klass = classifyChunkResponse(res.status, body)\n\t\t\tif (klass === 'closed') {\n\t\t\t\tlogger.info(`chunk ${index}: server reports session closed; stopping`)\n\t\t\t\treturn 'closed'\n\t\t\t}\n\t\t\tif (klass === 'fatal') {\n\t\t\t\tlogger.error(`chunk ${index} rejected with ${res.status}`)\n\t\t\t\treturn 'failed'\n\t\t\t}\n\t\t\t// 'retry' falls through to the backoff loop below.\n\t\t} catch (err) {\n\t\t\tlogger.warn(`chunk ${index} upload attempt ${attempt + 1} failed`, err)\n\t\t}\n\t\tattempt += 1\n\t\tconst backoff = Math.min(15000, 500 * 2 ** attempt) + Math.floor(Math.random() * 250)\n\t\tawait new Promise(resolve => setTimeout(resolve, backoff))\n\t}\n\treturn 'failed'\n}\n\nexport async function flushPendingFromIdb(store: RecorderStore, ctx: PluginContext): Promise<void> {\n\tif (!store.sessionId) return\n\tconst pending = await idbListChunks(store.sessionId)\n\tfor (const chunk of pending) {\n\t\tconst outcome = await uploadChunkWithRetry(chunk.apiUrl, chunk.sessionId, chunk.chunkIndex, chunk.blob, ctx.logger, 3)\n\t\tif (outcome === 'ok') {\n\t\t\tawait idbDeleteChunk(chunk.id)\n\t\t} else if (outcome === 'closed') {\n\t\t\t// Session closed server-side while draining the offline stash: stop the\n\t\t\t// flush and run the terminal close once. The stashed chunks can never\n\t\t\t// land now, but we leave them in IDB (harmless, swept by age) rather\n\t\t\t// than racing a delete during teardown.\n\t\t\thandleSessionClosed(store)\n\t\t\treturn\n\t\t}\n\t\t// 'failed': leave the chunk stashed for a future flush attempt.\n\t}\n}\n\n// Run the terminal close exactly once. Idempotent: the sessionClosed guard means\n// repeated calls (several queued chunks all rejected) only fire the handler the\n// first time. The actual stop/clear/ended-screen work lives in store.onSessionClosed,\n// wired by the entry module where the indicatorRoot is in scope.\nexport function handleSessionClosed(store: RecorderStore): void {\n\tif (store.sessionClosed) return\n\tstore.sessionClosed = true\n\tstore.onSessionClosed?.()\n}\n\nfunction enqueueChunk(store: RecorderStore, ctx: PluginContext, blob: Blob): void {\n\t// Once the server has told us the session is closed, drop any further chunks.\n\t// They can never land (post-finalise rejection), and stashing them for retry\n\t// would just spin against a closed session.\n\tif (store.cancelled || store.sessionClosed || !store.sessionId || blob.size === 0) return\n\tconst index = store.chunkIndex\n\tstore.chunkIndex += 1\n\t// Advance the persisted resume pointer so a hard navigation mid-recording\n\t// resumes from the next index, never re-using one already shipped. If the\n\t// page is pausing (store.paused, set before stopRecording) or finishing, the\n\t// trailing chunk stopRecording flushes must persist as 'paused' so pausedAt is\n\t// preserved and the RESUME_MAX_IDLE_MS idle gate engages; an 'active' write\n\t// here would clobber pausedAt.\n\tpersistActiveSession(store, store.paused || store.finishFlowRan ? 'paused' : 'active')\n\tstore.pendingUploads += 1\n\tconst sessionId = store.sessionId\n\tconst apiUrl = store.options.apiUrl\n\n\tstore.uploadQueue = store.uploadQueue.then(async () => {\n\t\t// A prior chunk in the queue may have already seen the close signal; skip\n\t\t// the upload entirely if so.\n\t\tif (store.sessionClosed) {\n\t\t\tstore.pendingUploads -= 1\n\t\t\treturn\n\t\t}\n\t\tconst outcome = await uploadChunkWithRetry(apiUrl, sessionId, index, blob, ctx.logger)\n\t\tif (outcome === 'closed') {\n\t\t\t// Definitive server \"session closed\". Stop recording, clear resume\n\t\t\t// state, show the ended screen. Do NOT stash: the chunk can't land.\n\t\t\thandleSessionClosed(store)\n\t\t} else if (outcome === 'failed') {\n\t\t\tctx.logger.warn(`chunk ${index} stashed for offline retry`)\n\t\t\tawait idbStashChunk({\n\t\t\t\tid: `${sessionId}:${index}:${Date.now()}`,\n\t\t\t\tsessionId,\n\t\t\t\tapiUrl,\n\t\t\t\tchunkIndex: index,\n\t\t\t\tblob,\n\t\t\t\tcreatedAt: Date.now(),\n\t\t\t})\n\t\t}\n\t\tstore.pendingUploads -= 1\n\t})\n}\n\n// ---------------------------------------------------------------------------\n// Silent-microphone guard\n//\n// A dead mic, or a virtual audio input device that macOS hands Chrome (e.g.\n// \"Background Music\", a Zoom/Teams virtual mic), delivers digital silence.\n// getUserMedia succeeds, MediaRecorder records, and 15 minutes of nothing\n// reaches the researcher with no warning. We DETECT a silent input stream and\n// WARN the participant (non-blocking: recording always continues).\n//\n// Detection runs a Web Audio AnalyserNode over the live track and computes RMS\n// in dBFS over a short window. The decision is a pure function so it can be\n// unit-tested without a real AudioContext.\n\n// Compute RMS in dBFS from normalized float time-domain samples (each in\n// [-1, 1], as produced by AnalyserNode.getFloatTimeDomainData). Returns a\n// finite dB value floored at SILENCE_FLOOR_DB so an all-zero window doesn't\n// yield -Infinity. Pure, no Web Audio needed: unit-tested directly.\nexport function rmsDbFromSamples(samples: Float32Array | number[]): number {\n\tconst n = samples.length\n\tif (n === 0) return SILENCE_FLOOR_DB\n\tlet sumSquares = 0\n\tfor (let i = 0; i < n; i += 1) {\n\t\tconst s = samples[i] ?? 0\n\t\tsumSquares += s * s\n\t}\n\tconst rms = Math.sqrt(sumSquares / n)\n\tif (rms <= 0) return SILENCE_FLOOR_DB\n\tconst db = 20 * Math.log10(rms)\n\treturn db < SILENCE_FLOOR_DB ? SILENCE_FLOOR_DB : db\n}\n\n// Pure silence decision. Accepts EITHER an already-computed RMS dB value (a\n// number) OR a sample window (array). True only when the level is at/below the\n// conservative silence threshold. A real voice, even a quiet one (-40 to\n// -50 dB), is comfortably above the line and returns false.\nexport function isStreamSilent(input: number | Float32Array | number[]): boolean {\n\tconst rmsDb = typeof input === 'number' ? input : rmsDbFromSamples(input)\n\treturn rmsDb <= SILENCE_RMS_DB_THRESHOLD\n}\n\nfunction startSilenceMonitor(stream: MediaStream, onChange: (silent: boolean) => void, logger: PluginContext['logger']): SilenceMonitor | null {\n\tconst Ctor: typeof AudioContext | undefined =\n\t\ttypeof window !== 'undefined'\n\t\t\t? (window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext)\n\t\t\t: undefined\n\tif (!Ctor) return null\n\n\tlet audioCtx: AudioContext\n\tlet source: MediaStreamAudioSourceNode\n\tlet analyser: AnalyserNode\n\ttry {\n\t\taudioCtx = new Ctor()\n\t\tsource = audioCtx.createMediaStreamSource(stream)\n\t\tanalyser = audioCtx.createAnalyser()\n\t\tanalyser.fftSize = 2048\n\t\t// We do not connect the analyser to audioCtx.destination: we only READ\n\t\t// the track, never play it back (that would echo the participant's own\n\t\t// mic into their speakers).\n\t\tsource.connect(analyser)\n\t} catch (err) {\n\t\tlogger.warn('silence monitor: failed to attach analyser', err)\n\t\treturn null\n\t}\n\n\tconst buffer = new Float32Array(analyser.fftSize)\n\t// Reported state (what the pill currently shows). Starts audible; we only\n\t// flip to silent after SILENCE_SUSTAINED_MS of continuous silence.\n\tlet reportedSilent = false\n\t// Timestamp the CURRENT run of same-classification readings began.\n\tlet runStartedAt = Date.now()\n\tlet lastRaw = false\n\tlet intervalId: ReturnType<typeof setInterval> | null = null\n\n\tconst tick = (): void => {\n\t\t// getFloatTimeDomainData is widely supported; guard for older engines.\n\t\ttry {\n\t\t\tanalyser.getFloatTimeDomainData(buffer)\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tconst rawSilent = isStreamSilent(buffer)\n\t\tconst now = Date.now()\n\t\tif (rawSilent !== lastRaw) {\n\t\t\tlastRaw = rawSilent\n\t\t\trunStartedAt = now\n\t\t}\n\t\t// Only commit a state change once the raw classification has held for\n\t\t// the sustained window. Going audible clears the warning immediately\n\t\t// once sustained, going silent raises it once sustained.\n\t\tif (rawSilent !== reportedSilent && now - runStartedAt >= SILENCE_SUSTAINED_MS) {\n\t\t\treportedSilent = rawSilent\n\t\t\tonChange(reportedSilent)\n\t\t}\n\t}\n\n\tintervalId = setInterval(tick, SILENCE_POLL_MS)\n\n\treturn {\n\t\tstop(): void {\n\t\t\tif (intervalId !== null) {\n\t\t\t\tclearInterval(intervalId)\n\t\t\t\tintervalId = null\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tsource.disconnect()\n\t\t\t\tanalyser.disconnect()\n\t\t\t} catch {\n\t\t\t\t// nodes may already be torn down\n\t\t\t}\n\t\t\t// close() returns a promise; failures here are harmless (context may\n\t\t\t// already be closing on page unload).\n\t\t\ttry {\n\t\t\t\tvoid audioCtx.close()\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t},\n\t}\n}\n\nexport async function startRecording(store: RecorderStore, ctx: PluginContext): Promise<void> {\n\t// Re-entrant: the failed chip re-invokes this to retry. Reset to the\n\t// pending state so the chip shows \"connecting\" again during the attempt.\n\tstore.micAcquiring = true\n\tstore.micFailReason = null\n\t// A retry tears down a prior monitor and clears the silent flag so the\n\t// fresh attempt starts from a clean state.\n\tif (store.silenceMonitor) {\n\t\tstore.silenceMonitor.stop()\n\t\tstore.silenceMonitor = null\n\t}\n\tstore.micSilent = false\n\trenderIndicatorState(store)\n\tif (!isMediaRecorderSupported()) {\n\t\tctx.logger.warn('MediaRecorder not supported, continuing without audio')\n\t\tstore.micAcquiring = false\n\t\tstore.micFailReason = 'unsupported'\n\t\tstore.indicatorState = 'no-audio'\n\t\trenderIndicatorState(store)\n\t\treturn\n\t}\n\tlet stream: MediaStream\n\ttry {\n\t\tstream = await navigator.mediaDevices.getUserMedia({ audio: true })\n\t} catch (err) {\n\t\tctx.logger.warn('mic permission denied or unavailable', err)\n\t\tstore.micAcquiring = false\n\t\t// Distinguish denied (blocked) from no-device (not-found) for copy.\n\t\tconst name = err instanceof Error ? err.name : ''\n\t\tstore.micFailReason = name === 'NotFoundError' || name === 'DevicesNotFoundError' ? 'not-found' : 'blocked'\n\t\tstore.indicatorState = 'no-audio'\n\t\trenderIndicatorState(store)\n\t\treturn\n\t}\n\tstore.stream = stream\n\tstore.hasMicPermission = true\n\tstore.micAcquiring = false\n\tstore.micFailReason = null\n\tconst mimeType = pickMimeType()\n\tlet recorder: MediaRecorder\n\ttry {\n\t\trecorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream)\n\t} catch (err) {\n\t\tctx.logger.error('MediaRecorder construction failed', err)\n\t\tstream.getTracks().forEach(t => t.stop())\n\t\tstore.stream = null\n\t\tstore.hasMicPermission = false\n\t\tstore.micAcquiring = false\n\t\tstore.micFailReason = 'unsupported'\n\t\tstore.indicatorState = 'no-audio'\n\t\trenderIndicatorState(store)\n\t\treturn\n\t}\n\tstore.recorder = recorder\n\trecorder.addEventListener('dataavailable', event => {\n\t\tif (event.data && event.data.size > 0) {\n\t\t\tenqueueChunk(store, ctx, event.data)\n\t\t}\n\t})\n\trecorder.addEventListener('error', event => {\n\t\tctx.logger.error('MediaRecorder error', event)\n\t})\n\t// `timeslice` makes the recorder emit a self-contained chunk every N ms.\n\trecorder.start(store.options.chunkSeconds * 1000)\n\t// On a successful (re)acquire, restore the live recording indicator. A\n\t// prior failed attempt left indicatorState at 'no-audio' (steady amber\n\t// dot); now that audio is live the dot should pulse red again.\n\tif (store.indicatorState === 'no-audio') {\n\t\tstore.indicatorState = 'recording'\n\t}\n\trenderIndicatorState(store)\n\n\t// Start the silent-mic guard over the live track. Detects a dead or virtual\n\t// silent input at record start and keeps checking for a mid-session mic\n\t// death. Warning only; recording is never gated on it. When the participant\n\t// has muted themselves the track is intentionally silent, so we suppress the\n\t// warning in that case (see the onChange guard).\n\tconst monitor = startSilenceMonitor(\n\t\tstream,\n\t\tsilent => {\n\t\t\t// Ignore the analyser while the participant has deliberately muted:\n\t\t\t// a muted track reads as silence by design, that's not a fault.\n\t\t\tconst effectiveSilent = silent && !store.muted\n\t\t\tif (store.micSilent === effectiveSilent) return\n\t\t\tstore.micSilent = effectiveSilent\n\t\t\trenderIndicatorState(store)\n\t\t},\n\t\tctx.logger,\n\t)\n\tstore.silenceMonitor = monitor\n}\n\nexport function toggleMute(store: RecorderStore): boolean {\n\tif (!store.stream || !store.hasMicPermission) return false\n\tconst tracks = store.stream.getAudioTracks()\n\tif (tracks.length === 0) return false\n\tconst nowMs = Date.now() - store.startedAt\n\tif (!store.muted) {\n\t\t// Going muted: disable each audio track. MediaRecorder keeps running;\n\t\t// the disabled track produces silence in the resulting WebM. We do NOT\n\t\t// pause the recorder so the single-stream lifecycle stays simple.\n\t\tfor (const t of tracks) t.enabled = false\n\t\tstore.muted = true\n\t\tstore.mutedSinceMs = nowMs\n\t} else {\n\t\t// Coming back: close the muted segment, re-enable.\n\t\tconst startMs = store.mutedSinceMs ?? nowMs\n\t\tif (nowMs > startMs) {\n\t\t\tstore.mutedSegments.push({ startMs, endMs: nowMs })\n\t\t}\n\t\tstore.mutedSinceMs = null\n\t\tstore.muted = false\n\t\tfor (const t of tracks) t.enabled = true\n\t}\n\treturn true\n}\n\nexport function flushMuteIfActive(store: RecorderStore): void {\n\tif (!store.muted || store.mutedSinceMs === null) return\n\tconst nowMs = Date.now() - store.startedAt\n\tif (nowMs > store.mutedSinceMs) {\n\t\tstore.mutedSegments.push({ startMs: store.mutedSinceMs, endMs: nowMs })\n\t}\n\tstore.mutedSinceMs = null\n}\n\nexport function stopRecording(store: RecorderStore): void {\n\tif (store.silenceMonitor) {\n\t\tstore.silenceMonitor.stop()\n\t\tstore.silenceMonitor = null\n\t}\n\tstore.micSilent = false\n\tconst recorder = store.recorder\n\tif (recorder && recorder.state !== 'inactive') {\n\t\ttry {\n\t\t\trecorder.requestData()\n\t\t} catch {\n\t\t\t// requestData throws if state is invalid; ignore.\n\t\t}\n\t\ttry {\n\t\t\trecorder.stop()\n\t\t} catch {\n\t\t\t// already stopped\n\t\t}\n\t}\n\tstore.recorder = null\n\tif (store.stream) {\n\t\tstore.stream.getTracks().forEach(t => t.stop())\n\t\tstore.stream = null\n\t}\n}\n","// Pause + finish orchestration for the user-test plugin. pauseFlow handles a\n// hard navigation (pagehide / tab hidden) by stopping the recorder WITHOUT\n// finalising so the session can resume on return; finishFlow is the real\n// terminal path (explicit Finish tap) that drains uploads, finalises\n// server-side, and renders the thanks screen. Both tie together the recorder,\n// session, and ui modules.\n\nimport type { PluginContext } from '../../plugin'\nimport { flushMuteIfActive, flushPendingFromIdb, startRecording, stopRecording } from './recorder'\nimport { clearActiveSession, finaliseSession, persistActiveSession, postPayout } from './session'\nimport type { PaymentSummary, RecorderStore } from './shared'\nimport { renderIndicatorState, showThanksScreen } from './ui'\n\n// Pause the recording for a hard navigation (pagehide / tab hidden) WITHOUT\n// finalising. The browser is about to tear down the MediaStream, so we stop the\n// recorder and let its queued chunk uploads race the unload on `keepalive`. The\n// session stays open server-side; it resumes on return (storage carries the\n// resume pointer), or the server-side stale sweep finalises it after ~10 min if\n// the participant never comes back. This replaces the old pagehide->finalise,\n// which permanently closed a session the participant intended to continue.\n//\n// Idempotent + non-destructive: it does NOT set finishFlowRan or move the\n// indicator to a terminal state, so a real Finish tap after resume still works.\n// Safe to call repeatedly (stopRecording tolerates an already-stopped recorder).\nexport function pauseFlow(store: RecorderStore): void {\n\tif (store.cancelled) return\n\t// Already finishing/finished for real: nothing to pause.\n\tif (store.finishFlowRan || store.indicatorState === 'finishing' || store.indicatorState === 'done') return\n\t// No live session yet (still creating/adopting): nothing to persist or stop.\n\tif (!store.sessionId) return\n\t// Flag paused BEFORE stopRecording so the final trailing `dataavailable` chunk\n\t// that recorder.requestData()/stop() flushes (which routes through\n\t// enqueueChunk -> persistActiveSession) writes 'paused' and keeps pausedAt,\n\t// rather than overwriting the entry with 'active' and dropping the idle clock.\n\tstore.paused = true\n\tflushMuteIfActive(store)\n\tstopRecording(store)\n\t// Mark the persisted state paused at the current chunk index. The queued\n\t// uploads (each `keepalive` where small enough) continue draining as the\n\t// page unloads; the resume picks up from store.chunkIndex.\n\tpersistActiveSession(store, 'paused')\n}\n\nexport async function finishFlow(store: RecorderStore, ctx: PluginContext, opts: { showThanks: boolean }): Promise<void> {\n\tif (store.cancelled) return\n\t// Short-circuit re-entry. The pagehide handler and the manual Finish click\n\t// can race; server is also idempotent on a second finalise call, but doing\n\t// the local work twice (flushing mute, draining queues, etc.) is wasted.\n\tif (store.finishFlowRan) return\n\tif (store.indicatorState === 'finishing' || store.indicatorState === 'done') return\n\tstore.finishFlowRan = true\n\tstore.indicatorState = 'finishing'\n\tflushMuteIfActive(store)\n\trenderIndicatorState(store)\n\n\tstopRecording(store)\n\t// Wait for the queued uploads to drain. Each upload already has its own\n\t// retry/backoff; this just lets them finish before finalise fires.\n\tawait store.uploadQueue\n\tawait flushPendingFromIdb(store, ctx)\n\n\tconst durationSeconds = (Date.now() - store.startedAt) / 1000\n\t// Replay linkage, computed once and sent on every finalise call for this\n\t// session. sdkSessionId is the core-owned per-tab id (the primary key the\n\t// server uses to resolve the SessionReplay); replayOffsetMs is the offset\n\t// captured at session start, only set when replay was active. Both degrade\n\t// gracefully to absent. Sending it on the second (end-note) finalise too\n\t// is harmless: the server stores idempotently.\n\tconst replayLinkage: { sdkSessionId?: string; replayOffsetMs?: number } = {}\n\tconst linkageSdkSessionId = ctx.getSdkSessionId ? ctx.getSdkSessionId() : undefined\n\tif (linkageSdkSessionId) replayLinkage.sdkSessionId = linkageSdkSessionId\n\tif (store.replayOffsetAtStartMs !== null) {\n\t\treplayLinkage.replayOffsetMs = store.replayOffsetAtStartMs\n\t}\n\t// Payment summary from the first finalise response drives the finished screen\n\t// (complete vs ended-early, reward headline, one-tap payout default).\n\tlet payment: PaymentSummary | null = null\n\tif (store.sessionId) {\n\t\t// First finalise carries durationSeconds + mutedSegments + any un-acked\n\t\t// notes (recovery channel). End-of-test note is sent via a second\n\t\t// finalise call from the thanks screen.\n\t\tconst unackedNotes = store.notes.filter(n => !n.acked).map(n => ({ atMs: n.atMs, text: n.text }))\n\t\tconst result = await finaliseSession(store.options.apiUrl, store.sessionId, durationSeconds, {\n\t\t\tmutedSegments: store.mutedSegments,\n\t\t\tnotes: unackedNotes,\n\t\t\t...replayLinkage,\n\t\t})\n\t\tif (result.ok) {\n\t\t\tpayment = result.payment\n\t\t\t// Mark the un-acked notes we just shipped as acked so the second\n\t\t\t// finalise call doesn't resend them.\n\t\t\tfor (const n of store.notes) {\n\t\t\t\tif (!n.acked) n.acked = true\n\t\t\t}\n\t\t}\n\t\tstore.indicatorState = result.ok ? 'done' : 'error'\n\t\t// Genuine, server-confirmed finalisation: clear the persisted resume\n\t\t// state so a later unrelated visit never tries to resume this closed\n\t\t// session. On error we keep it so the resume / server stale sweep can\n\t\t// still own the session.\n\t\tif (result.ok) clearActiveSession()\n\t} else {\n\t\tstore.indicatorState = 'error'\n\t}\n\trenderIndicatorState(store)\n\n\tif (opts.showThanks && store.indicatorRoot && store.indicatorState === 'done') {\n\t\tshowThanksScreen(store.indicatorRoot, {\n\t\t\tpayment,\n\t\t\tonPayout: async destination => {\n\t\t\t\tif (!store.sessionId) return false\n\t\t\t\treturn postPayout(store.options.apiUrl, store.sessionId, destination, ctx.logger)\n\t\t\t},\n\t\t\tonResume: () => {\n\t\t\t\t// Re-arm an ended-early session: the recording was already stopped +\n\t\t\t\t// finalised, so resuming starts a fresh recording leg under the same\n\t\t\t\t// sessionId. The server is idempotent on a later finalise; the new\n\t\t\t\t// audio chunks continue the same R2 prefix. Reset the finish guard\n\t\t\t\t// and timeline anchor so the next Finish re-evaluates completion.\n\t\t\t\tstore.finishFlowRan = false\n\t\t\t\tstore.indicatorState = 'recording'\n\t\t\t\tstore.startedAt = Date.now()\n\t\t\t\tstore.muted = false\n\t\t\t\tstore.mutedSinceMs = null\n\t\t\t\t// Re-arm the persisted resume state: the participant chose to keep\n\t\t\t\t// going, so a hard navigation during this new leg should resume too.\n\t\t\t\tpersistActiveSession(store, 'active')\n\t\t\t\trenderIndicatorState(store)\n\t\t\t\tvoid startRecording(store, ctx)\n\t\t\t},\n\t\t\tonSubmitNote: async text => {\n\t\t\t\tif (!store.sessionId) return\n\t\t\t\tstore.endNote = text\n\t\t\t\t// Second finalise only carries the late-binding fields. mutedSegments\n\t\t\t\t// already landed on call 1, server stores idempotently, no need to\n\t\t\t\t// resend. Include any notes that arrived (or failed to ack) between\n\t\t\t\t// the two calls.\n\t\t\t\tconst stillUnacked = store.notes.filter(n => !n.acked).map(n => ({ atMs: n.atMs, text: n.text }))\n\t\t\t\tconst result = await finaliseSession(store.options.apiUrl, store.sessionId, durationSeconds, {\n\t\t\t\t\tendNote: text,\n\t\t\t\t\tnotes: stillUnacked,\n\t\t\t\t\t...replayLinkage,\n\t\t\t\t})\n\t\t\t\tif (!result.ok) throw new Error('finalise failed')\n\t\t\t\tfor (const n of store.notes) {\n\t\t\t\t\tif (!n.acked) n.acked = true\n\t\t\t\t}\n\t\t\t},\n\t\t\tonSkip: () => { /* nothing to send */ },\n\t\t})\n\t}\n}\n","// User-testing-mode plugin for the Usero widget.\n//\n// Activates ONLY when the host page URL carries `?usero_test=<slug>`. When\n// active, it:\n// 1. Creates a UserTestSession on the SaaS side via\n// POST /api/user-test-sessions\n// 2. Starts a MediaRecorder on the user's microphone (Opus in WebM) and\n// ships chunks every `chunkSeconds` to\n// PUT /api/user-test-sessions/:id/chunk?index=N\n// 3. Renders a small floating \"recording\" indicator with a Finish button\n// bottom-center so the tester can stop on their own.\n// 4. On Finish, flushes any buffered chunks and calls\n// POST /api/user-test-sessions/:id/finalise.\n//\n// Resume across hard navigations: `pagehide` / `visibilitychange -> hidden` now\n// PAUSE rather than finalise. The recorder stops and queued chunks drain on\n// `keepalive`, but the session stays open server-side. Resume state\n// ({ slug, sessionId, nextChunkIndex, startedAt }) is persisted per-origin in\n// localStorage (`usero:user-test:active-session`). On init, a non-stale entry\n// (< 2h old) makes the plugin re-adopt that session and continue recording from\n// nextChunkIndex, even when the URL no longer carries `?usero_test`/`uts` (e.g.\n// after an OAuth round-trip). The mic permission is already granted on the same\n// origin, so no second prompt. The rrweb DOM recorder resumes on its own: the\n// session-replay plugin re-inits on the fresh document, reuses the same\n// sdkSessionId from sessionStorage, and the server stitches the replay across\n// the gap. Finalisation comes only from an explicit Finish or the server-side\n// stale-session sweep (~10 min of no chunks); see recoverStuckUserTestSessions\n// in the feedback repo.\n//\n// Mic-permission denied is non-fatal: the session is still created (the\n// session-replay plugin keeps recording in parallel) and the indicator\n// shows a \"no audio\" hint. The SaaS side detects audio presence from the\n// first chunk; if no chunks land, hasAudio stays false there too.\n//\n// Bundle hygiene: this module is a subpath export and does NOT depend on\n// rrweb or any other heavy dep — just the native MediaRecorder API. The\n// floating UI is shadow-DOM scoped so host page CSS can't leak in.\n//\n// Privacy: only the microphone is recorded. We never read DOM text, never\n// touch the camera, and never persist audio in the browser past the\n// IndexedDB fallback (cleared on successful upload).\n//\n// This file is the thin entry that composes the modules under\n// `./user-test/`: shared (types/constants/icons), session (resume state +\n// network), recorder (capture lifecycle + silence guard), ui (indicator +\n// screens), and lifecycle (pause/finish orchestration).\n\nimport type { UseroPlugin } from '../plugin'\nimport { DEFAULT_API_URL } from '../types'\nimport { finishFlow, pauseFlow } from './user-test/lifecycle'\nimport {\n\tclassifyChunkResponse,\n\thandleSessionClosed,\n\tisMediaRecorderSupported,\n\tisStreamSilent,\n\tpickMimeType,\n\trmsDbFromSamples,\n\tstartRecording,\n\tstopRecording,\n\ttoggleMute,\n} from './user-test/recorder'\nimport {\n\tadoptSession,\n\tclearActiveSession,\n\tcreateSession,\n\tgetAdoptSessionId,\n\tgetTestSlug,\n\tparseActiveSession,\n\tpersistActiveSession,\n\tpostNoteWithRetry,\n\treadActiveSession,\n\treadTesterName,\n} from './user-test/session'\nimport {\n\tACTIVE_SESSION_MAX_AGE_MS,\n\tACTIVE_SESSION_STORAGE_KEY,\n\tDEFAULT_OPTIONS,\n\ttype InFlightNote,\n\ttype RecorderStore,\n\tRESUME_MAX_IDLE_MS,\n\tSILENCE_FLOOR_DB,\n\tSILENCE_RMS_DB_THRESHOLD,\n\ttype UserTestOptions,\n\ttype UserTestTask,\n} from './user-test/shared'\nimport {\n\tbuildIndicator,\n\tcloseNotePopover,\n\tinstallTasksToggle,\n\tmicChipState,\n\topenNotePopover,\n\treadTasksPanelOpen,\n\trenderIndicatorState,\n\trenderNotesCount,\n\trenderTasksPanel,\n\tshowMuteToast,\n\tshowResumedToast,\n\tshowSessionEndedScreen,\n\twriteTasksPanelOpen,\n} from './user-test/ui'\n\nexport type { UserTestOptions } from './user-test/shared'\n// Pure, side-effect-free silence helpers. Re-exported at the package's public\n// surface (they were top-level exports of this module before the split).\nexport { isStreamSilent, rmsDbFromSamples } from './user-test/recorder'\n\nexport function userTest(options: UserTestOptions = {}): UseroPlugin {\n\tconst merged: Required<UserTestOptions> = {\n\t\tqueryParam: options.queryParam ?? DEFAULT_OPTIONS.queryParam,\n\t\tchunkSeconds: options.chunkSeconds ?? DEFAULT_OPTIONS.chunkSeconds,\n\t\tapiUrl: options.apiUrl ?? DEFAULT_OPTIONS.apiUrl,\n\t\ttesterName: options.testerName ?? DEFAULT_OPTIONS.testerName,\n\t\thideIndicator: options.hideIndicator ?? DEFAULT_OPTIONS.hideIndicator,\n\t}\n\n\treturn {\n\t\tname: 'user-test',\n\t\tonInit(ctx) {\n\t\t\tif (typeof window === 'undefined' || typeof document === 'undefined') return\n\t\t\tconst urlSlug = getTestSlug(merged.queryParam)\n\t\t\t// Resume source of truth: a non-stale persisted session for THIS origin\n\t\t\t// means a test is in progress, even when the URL no longer carries\n\t\t\t// `?usero_test`/`uts` (the common case after an OAuth round-trip strips\n\t\t\t// the params). We resume it unless the URL explicitly names a DIFFERENT\n\t\t\t// slug (a fresh test on the same origin supersedes a stale-but-in-window\n\t\t\t// leftover, so we drop the old one rather than resuming the wrong test).\n\t\t\tconst resumeState = readActiveSession()\n\t\t\tconst resumable = resumeState && (!urlSlug || urlSlug === resumeState.slug) ? resumeState : null\n\t\t\tif (resumeState && !resumable) {\n\t\t\t\t// URL names a different test than the persisted one: the old session\n\t\t\t\t// is abandoned in favour of the new test. Clear it so we don't leave\n\t\t\t\t// a dangling resume pointer (the server stale sweep finalises it).\n\t\t\t\tclearActiveSession()\n\t\t\t}\n\n\t\t\tconst slug = resumable?.slug ?? urlSlug\n\t\t\tif (!slug) return\n\t\t\tconst isResume = resumable !== null\n\n\t\t\t// CRITICAL replay-link fix: re-seat the per-tab sdkSessionId from the\n\t\t\t// durable resume state SYNCHRONOUSLY, before anything else. A cross-origin\n\t\t\t// hard nav wiped sessionStorage, so without this the resumed session-replay\n\t\t\t// plugin would mint a NEW sdkSessionId; the post-nav replay row would then\n\t\t\t// no longer share an id with this audio session, the finalise\n\t\t\t// (clientId, sdkSessionId) join would miss, and the dashboard would hide a\n\t\t\t// perfectly-recorded replay. reseatSdkSessionId writes sessionStorage AND\n\t\t\t// the core cache, so it corrects the id whether session-replay's onInit\n\t\t\t// runs after this (reads the re-seated value) or already ran (cache fixed,\n\t\t\t// and its own finalise reads through the cache). No-ops on a bad/absent id.\n\t\t\tif (isResume && resumable?.sdkSessionId && ctx.reseatSdkSessionId) {\n\t\t\t\tctx.reseatSdkSessionId(resumable.sdkSessionId)\n\t\t\t}\n\n\t\t\tconst apiUrl = merged.apiUrl || ctx.baseUrl || DEFAULT_API_URL\n\t\t\tconst store: RecorderStore = {\n\t\t\t\tcancelled: false,\n\t\t\t\tslug,\n\t\t\t\tsessionId: null,\n\t\t\t\tclientId: null,\n\t\t\t\trecorder: null,\n\t\t\t\tstream: null,\n\t\t\t\t// On resume, continue the chunk index so we never overwrite a chunk\n\t\t\t\t// already shipped in the pre-navigation leg.\n\t\t\t\tchunkIndex: resumable?.nextChunkIndex ?? 0,\n\t\t\t\tuploadQueue: Promise.resolve(),\n\t\t\t\tpendingUploads: 0,\n\t\t\t\t// On resume, keep the ORIGINAL session start so duration + staleness\n\t\t\t\t// stay anchored to when the test actually began, not the return moment.\n\t\t\t\tstartedAt: resumable?.startedAt ?? Date.now(),\n\t\t\t\tindicator: null,\n\t\t\t\tindicatorRoot: null,\n\t\t\t\tindicatorState: 'recording',\n\t\t\t\tpageHideHandler: null,\n\t\t\t\tvisibilityHandler: null,\n\t\t\t\toptions: { ...merged, apiUrl },\n\t\t\t\ttasks: [],\n\t\t\t\ttasksPanelOpen: readTasksPanelOpen(),\n\t\t\t\toutsidePointerHandler: null,\n\t\t\t\tkeydownHandler: null,\n\t\t\t\thasMicPermission: false,\n\t\t\t\tmicAcquiring: true,\n\t\t\t\tmicFailReason: null,\n\t\t\t\tmuted: false,\n\t\t\t\tmutedSinceMs: null,\n\t\t\t\tmutedSegments: [],\n\t\t\t\tmicSilent: false,\n\t\t\t\tsilenceMonitor: null,\n\t\t\t\tmuteToastShown: false,\n\t\t\t\tmuteToastTimers: [],\n\t\t\t\tresumeToastTimers: [],\n\t\t\t\tnotes: [],\n\t\t\t\tnotesPopoverOpen: false,\n\t\t\t\tnotePopoverAtMs: null,\n\t\t\t\tendNote: '',\n\t\t\t\tfinishFlowRan: false,\n\t\t\t\tsessionClosed: false,\n\t\t\t\tonSessionClosed: null,\n\t\t\t\tpaused: false,\n\t\t\t\tresumed: isResume,\n\t\t\t\tsdkSessionId: null,\n\t\t\t\treplayOffsetAtStartMs: null,\n\t\t\t}\n\t\t\tctx.setStore(store)\n\n\t\t\t// Terminal close handler invoked by the chunk-upload path when the\n\t\t\t// server reports the session is already closed (409 + closeResume) while\n\t\t\t// recording, e.g. finalise was triggered elsewhere or the stale sweep\n\t\t\t// closed it. Mirror the adopt-time 'closed' branch below: stop recording,\n\t\t\t// clear the resume pointer so a later visit never resurrects it, and show\n\t\t\t// the honest terminal \"this test session ended\" notice. No retries.\n\t\t\tstore.onSessionClosed = (): void => {\n\t\t\t\tif (store.cancelled) return\n\t\t\t\tctx.logger.info('user-test session closed by server during upload; stopping recording')\n\t\t\t\tstopRecording(store)\n\t\t\t\tclearActiveSession()\n\t\t\t\tstore.indicatorState = 'done'\n\t\t\t\tif (store.indicatorRoot && !merged.hideIndicator) {\n\t\t\t\t\tshowSessionEndedScreen(store.indicatorRoot)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst onFinish = (): void => {\n\t\t\t\tvoid finishFlow(store, ctx, { showThanks: true })\n\t\t\t}\n\n\t\t\tconst setPanelOpen = (open: boolean): void => {\n\t\t\t\tif (store.tasksPanelOpen === open) return\n\t\t\t\tstore.tasksPanelOpen = open\n\t\t\t\twriteTasksPanelOpen(open)\n\t\t\t\trenderTasksPanel(store)\n\t\t\t}\n\n\t\t\tconst onToggleTasks = (): void => setPanelOpen(!store.tasksPanelOpen)\n\n\t\t\tconst onToggleMute = (): void => {\n\t\t\t\tif (!store.hasMicPermission) {\n\t\t\t\t\t// In the failed terminal state the chip is a retry button:\n\t\t\t\t\t// re-attempt mic acquisition. Ignore taps while still\n\t\t\t\t\t// acquiring (connecting state is unfocusable anyway).\n\t\t\t\t\tif (!store.micAcquiring && store.indicatorState !== 'finishing' && store.indicatorState !== 'done' && store.indicatorState !== 'error') {\n\t\t\t\t\t\tvoid startRecording(store, ctx)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Silent-mic warning is also a retry affordance: the participant\n\t\t\t\t// has likely switched their input device, so re-acquire the mic\n\t\t\t\t// rather than toggling mute. startRecording tears down the old\n\t\t\t\t// stream + monitor and re-runs detection on the fresh track.\n\t\t\t\tif (store.micSilent && !store.micAcquiring && store.indicatorState !== 'finishing' && store.indicatorState !== 'done' && store.indicatorState !== 'error') {\n\t\t\t\t\tvoid startRecording(store, ctx)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst ok = toggleMute(store)\n\t\t\t\tif (!ok) return\n\t\t\t\tif (store.muted) {\n\t\t\t\t\t// Muting is intentional silence: drop any active silent-mic\n\t\t\t\t\t// warning so the two states don't fight. The monitor's\n\t\t\t\t\t// onChange suppresses re-raising it while muted.\n\t\t\t\t\tstore.micSilent = false\n\t\t\t\t\tshowMuteToast(store)\n\t\t\t\t}\n\t\t\t\trenderIndicatorState(store)\n\t\t\t}\n\n\t\t\tconst closeNote = (): void => closeNotePopover(store)\n\t\t\tconst onOpenNote = (): void => {\n\t\t\t\tif (store.notesPopoverOpen) { closeNote(); return }\n\t\t\t\topenNotePopover(\n\t\t\t\t\tstore,\n\t\t\t\t\ttext => {\n\t\t\t\t\t\tconst atMs = store.notePopoverAtMs ?? Math.max(0, Date.now() - store.startedAt)\n\t\t\t\t\t\tconst note: InFlightNote = { atMs, text, acked: false }\n\t\t\t\t\t\tstore.notes.push(note)\n\t\t\t\t\t\tcloseNote()\n\t\t\t\t\t\trenderNotesCount(store)\n\t\t\t\t\t\t// UI never blocks on the POST. On success we mark the note\n\t\t\t\t\t\t// acked; on failure (after one retry) it stays unacked and\n\t\t\t\t\t\t// gets included in the finalise notes batch as a recovery\n\t\t\t\t\t\t// channel. Server dedupes by (sessionId, atMs, text).\n\t\t\t\t\t\tif (store.sessionId) {\n\t\t\t\t\t\t\tconst sessionId = store.sessionId\n\t\t\t\t\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\t\t\t\t\tconst result = await postNoteWithRetry(store.options.apiUrl, sessionId, atMs, text, ctx.logger)\n\t\t\t\t\t\t\t\tif (result.ok) {\n\t\t\t\t\t\t\t\t\tnote.acked = true\n\t\t\t\t\t\t\t\t\tif (result.id) note.serverId = result.id\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t() => closeNote(),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif (!merged.hideIndicator) {\n\t\t\t\tconst host = document.createElement('div')\n\t\t\t\thost.setAttribute('data-usero-user-test', 'true')\n\t\t\t\tdocument.body.appendChild(host)\n\t\t\t\tstore.indicator = host\n\t\t\t\tstore.indicatorRoot = buildIndicator(host, store, {\n\t\t\t\t\tonFinish,\n\t\t\t\t\tonToggleTasks,\n\t\t\t\t\tonToggleMute,\n\t\t\t\t\tonOpenNote,\n\t\t\t\t})\n\t\t\t\trenderIndicatorState(store)\n\t\t\t\trenderNotesCount(store)\n\t\t\t}\n\n\t\t\t// Outside-click + Escape close the tasks panel. Listen on document\n\t\t\t// (composedPath checks shadow ancestry so taps on the panel/pill\n\t\t\t// itself don't dismiss).\n\t\t\tconst outsidePointer = (event: PointerEvent): void => {\n\t\t\t\tconst host = store.indicator\n\t\t\t\tif (!host) return\n\t\t\t\tconst path = event.composedPath()\n\t\t\t\tif (path.includes(host)) return\n\t\t\t\tif (store.tasksPanelOpen) setPanelOpen(false)\n\t\t\t\tif (store.notesPopoverOpen) closeNote()\n\t\t\t}\n\t\t\tconst onKeydown = (event: KeyboardEvent): void => {\n\t\t\t\tif (event.key !== 'Escape') return\n\t\t\t\tif (store.tasksPanelOpen) setPanelOpen(false)\n\t\t\t\tif (store.notesPopoverOpen) closeNote()\n\t\t\t}\n\t\t\tstore.outsidePointerHandler = outsidePointer\n\t\t\tstore.keydownHandler = onKeydown\n\t\t\tdocument.addEventListener('pointerdown', outsidePointer, true)\n\t\t\tdocument.addEventListener('keydown', onKeydown)\n\n\t\t\tconst pageHide = (): void => {\n\t\t\t\t// PAUSE, do not finalise. A hard navigation (e.g. an OAuth round\n\t\t\t\t// trip) destroys the MediaStream, but the session must survive so\n\t\t\t\t// recording resumes on return. We stop the recorder, mark the\n\t\t\t\t// persisted state paused, and let queued chunk uploads race the\n\t\t\t\t// unload on `keepalive`. Finalisation now comes only from an\n\t\t\t\t// explicit Finish tap or the server-side stale sweep (~10 min of\n\t\t\t\t// no chunks). See pauseFlow.\n\t\t\t\tpauseFlow(store)\n\t\t\t}\n\t\t\tstore.pageHideHandler = pageHide\n\t\t\twindow.addEventListener('pagehide', pageHide)\n\n\t\t\t// visibilitychange -> hidden is the reliable mobile backstop: iOS\n\t\t\t// frequently backgrounds/kills a tab WITHOUT firing pagehide. It runs\n\t\t\t// the SAME pause path; pauseFlow is idempotent and non-terminal, so a\n\t\t\t// later Finish tap (or a resume) still works after the tab returns to\n\t\t\t// the foreground. We don't finalise here either, for the same reason.\n\t\t\tconst onVisibilityChange = (): void => {\n\t\t\t\tif (document.visibilityState !== 'hidden') return\n\t\t\t\tpauseFlow(store)\n\t\t\t}\n\t\t\tstore.visibilityHandler = onVisibilityChange\n\t\t\tdocument.addEventListener('visibilitychange', onVisibilityChange)\n\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\t// Resolve the session for this leg. Three paths:\n\t\t\t\t// 1. RESUME (storage has a non-stale session): ADOPT the existing\n\t\t\t\t// id. Never create a new one. Works even when the URL lost its\n\t\t\t\t// `?usero_test`/`uts` params after a hard navigation.\n\t\t\t\t// 2. Entry-screen created session (`uts` in URL): ADOPT it.\n\t\t\t\t// 3. Open test (old link, no uts): CREATE a fresh session.\n\t\t\t\t// adopt is an idempotent GET, so re-adopting a still-active session\n\t\t\t\t// on resume is safe and does not disturb its server state.\n\t\t\t\tconst adoptId = resumable?.sessionId ?? getAdoptSessionId()\n\t\t\t\tlet created: { sessionId: string; clientId: string; tasks: UserTestTask[] } | null\n\t\t\t\tif (adoptId) {\n\t\t\t\t\tconst adopted = await adoptSession(apiUrl, adoptId)\n\t\t\t\t\tif (store.cancelled) return\n\t\t\t\t\tif (adopted.kind === 'closed') {\n\t\t\t\t\t\t// The server says this session is already finalised/failed (e.g.\n\t\t\t\t\t\t// closed by the stale sweep). Resume must NOT resurrect it and\n\t\t\t\t\t\t// upload post-finalise chunks. Clear the resume pointer and stop:\n\t\t\t\t\t\t// no recording, no error UI (the test is legitimately over).\n\t\t\t\t\t\tctx.logger.info('user-test session already closed on adopt; not resuming')\n\t\t\t\t\t\tclearActiveSession()\n\t\t\t\t\t\t// Don't leave the participant on a silent page with no idea the\n\t\t\t\t\t\t// test ended. Show a brief, honest terminal notice (no actions,\n\t\t\t\t\t\t// nothing to resume). Skip when the indicator is suppressed.\n\t\t\t\t\t\tif (store.indicatorRoot && !merged.hideIndicator) {\n\t\t\t\t\t\t\tshowSessionEndedScreen(store.indicatorRoot)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif (adopted.kind === 'error') {\n\t\t\t\t\t\t// Transient adopt failure (fetch rejected / 5xx on flaky wifi),\n\t\t\t\t\t\t// NOT a server \"closed\". RETAIN the resume state so a reload can\n\t\t\t\t\t\t// retry; clearing it here would strand a still-live session. Only\n\t\t\t\t\t\t// a `closed` result (409/410, handled above) clears state. We stop\n\t\t\t\t\t\t// without recording this leg and show the retry-able error UI.\n\t\t\t\t\t\tctx.logger.warn('user-test adopt failed transiently on resume; keeping resume state for retry')\n\t\t\t\t\t\tstore.indicatorState = 'error'\n\t\t\t\t\t\trenderIndicatorState(store)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tcreated = adopted.kind === 'ok' ? adopted : null\n\t\t\t\t} else {\n\t\t\t\t\tcreated = await createSession(apiUrl, slug, readTesterName(merged.testerName))\n\t\t\t\t\tif (store.cancelled) return\n\t\t\t\t}\n\t\t\t\tif (!created) {\n\t\t\t\t\tctx.logger.error(adoptId ? 'failed to adopt user-test session' : 'failed to create user-test session')\n\t\t\t\t\t// Resume's adopt failures are now fully handled above ('closed'\n\t\t\t\t\t// clears + returns; transient 'error' RETAINS state + returns), so a\n\t\t\t\t\t// resume never reaches here with created === null. This branch is the\n\t\t\t\t\t// createSession (fresh, non-resume) failure path. The isResume clear is\n\t\t\t\t\t// a defensive no-op kept in case a future code path falls through.\n\t\t\t\t\tif (isResume) clearActiveSession()\n\t\t\t\t\tstore.indicatorState = 'error'\n\t\t\t\t\trenderIndicatorState(store)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tstore.sessionId = created.sessionId\n\t\t\t\tstore.clientId = created.clientId\n\t\t\t\t// Resolve the per-tab sdkSessionId now (once) so it can be durably\n\t\t\t\t// mirrored into the resume state and sent on finalise. On resume this\n\t\t\t\t// is the SAME id we re-seated synchronously above (so the post-nav\n\t\t\t\t// replay row joins this audio session); on a fresh start it is the\n\t\t\t\t// tab's id. Null when replay/core accessor is absent (no link to keep).\n\t\t\t\tstore.sdkSessionId = ctx.getSdkSessionId ? ctx.getSdkSessionId() : null\n\t\t\t\t// Persist (or refresh) the resume pointer now that we have a session\n\t\t\t\t// id (and the sdkSessionId), BEFORE the first chunk flushes, so a hard\n\t\t\t\t// navigation in the first few seconds of recording still resumes AND\n\t\t\t\t// keeps the replay link.\n\t\t\t\tpersistActiveSession(store, 'active')\n\t\t\t\t// Capture the replay offset HERE at session start (not at\n\t\t\t\t// finalise) so it reflects when the test began relative to the\n\t\t\t\t// recording. The replay plugin publishes its start epoch into\n\t\t\t\t// the core; we read it via the context. If replay is not active\n\t\t\t\t// (plugin not loaded, sampled out, or an older host without the\n\t\t\t\t// accessor) we leave it null and the finalise body omits\n\t\t\t\t// replayOffsetMs. Anchored to store.startedAt (test start),\n\t\t\t\t// clamped >= 0 in case the test starts a hair before the replay\n\t\t\t\t// epoch is published.\n\t\t\t\tconst replayStartMs = ctx.getReplayStartMs ? ctx.getReplayStartMs() : null\n\t\t\t\tstore.replayOffsetAtStartMs =\n\t\t\t\t\treplayStartMs === null ? null : Math.max(0, store.startedAt - replayStartMs)\n\t\t\t\tstore.tasks = created.tasks\n\t\t\t\tif (store.tasks.length > 0 && store.indicatorRoot && !merged.hideIndicator) {\n\t\t\t\t\tconst bar = store.indicatorRoot.querySelector('.bar')\n\t\t\t\t\tconst finishBtn = bar?.querySelector('.finish-btn')\n\t\t\t\t\tif (bar instanceof HTMLElement && finishBtn instanceof HTMLElement && !bar.querySelector('.tasks-btn')) {\n\t\t\t\t\t\tinstallTasksToggle(bar, finishBtn, store, onToggleTasks)\n\t\t\t\t\t}\n\t\t\t\t\trenderTasksPanel(store)\n\t\t\t\t}\n\t\t\t\tawait startRecording(store, ctx)\n\t\t\t\trenderIndicatorState(store)\n\t\t\t\t// Recorder is live again. If this leg was a resume across a hard\n\t\t\t\t// navigation, give the participant a brief, unobtrusive confirmation\n\t\t\t\t// that the test is still recording. No-op on a fresh start (the guard\n\t\t\t\t// checks store.resumed and clears it so it fires at most once).\n\t\t\t\tshowResumedToast(store)\n\t\t\t})()\n\t\t},\n\t\tonDestroy(ctx) {\n\t\t\tconst store = ctx.getStore<RecorderStore>()\n\t\t\tif (!store) return\n\t\t\tstore.cancelled = true\n\t\t\tif (store.pageHideHandler) {\n\t\t\t\twindow.removeEventListener('pagehide', store.pageHideHandler)\n\t\t\t\tstore.pageHideHandler = null\n\t\t\t}\n\t\t\tif (store.visibilityHandler) {\n\t\t\t\tdocument.removeEventListener('visibilitychange', store.visibilityHandler)\n\t\t\t\tstore.visibilityHandler = null\n\t\t\t}\n\t\t\tstopRecording(store)\n\t\t\tif (store.outsidePointerHandler) {\n\t\t\t\tdocument.removeEventListener('pointerdown', store.outsidePointerHandler, true)\n\t\t\t\tstore.outsidePointerHandler = null\n\t\t\t}\n\t\t\tif (store.keydownHandler) {\n\t\t\t\tdocument.removeEventListener('keydown', store.keydownHandler)\n\t\t\t\tstore.keydownHandler = null\n\t\t\t}\n\t\t\tfor (const id of store.muteToastTimers) {\n\t\t\t\ttry { window.clearTimeout(id) } catch { /* ignore */ }\n\t\t\t}\n\t\t\tstore.muteToastTimers = []\n\t\t\tfor (const id of store.resumeToastTimers) {\n\t\t\t\ttry { window.clearTimeout(id) } catch { /* ignore */ }\n\t\t\t}\n\t\t\tstore.resumeToastTimers = []\n\t\t\tif (store.indicator && store.indicator.parentNode) {\n\t\t\t\tstore.indicator.parentNode.removeChild(store.indicator)\n\t\t\t}\n\t\t\tstore.indicator = null\n\t\t\tstore.indicatorRoot = null\n\t\t},\n\t}\n}\n\n// Internal helpers exposed for tests only. Not part of the public API.\nexport const __test__ = {\n\tgetTestSlug,\n\tpickMimeType,\n\tisMediaRecorderSupported,\n\tclassifyChunkResponse,\n\thandleSessionClosed,\n\tmicChipState,\n\tisStreamSilent,\n\trmsDbFromSamples,\n\tSILENCE_RMS_DB_THRESHOLD,\n\tSILENCE_FLOOR_DB,\n\tparseActiveSession,\n\treadActiveSession,\n\tclearActiveSession,\n\tpersistActiveSession,\n\tadoptSession,\n\tACTIVE_SESSION_MAX_AGE_MS,\n\tRESUME_MAX_IDLE_MS,\n\tACTIVE_SESSION_STORAGE_KEY,\n}\n"]}
|