@testerpaykit/react 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,876 @@
1
+ import React from 'react';
2
+ import { DeviceMetadata, AppMetadata, TesterPayKit } from '@testerpaykit/sdk';
3
+ export { ActionResult, AgentAction, WidgetInfoForLlm, discoverWidgets, executeAction, isSyntheticEvent, redactSensitive, resetWidgetIds } from '@testerpaykit/ai-tester';
4
+
5
+ interface TesterPayKitProviderProps {
6
+ apiKey: string;
7
+ projectId: string;
8
+ testerId: string;
9
+ deviceId: string;
10
+ deviceMetadata: DeviceMetadata;
11
+ appMetadata: AppMetadata;
12
+ baseURL?: string;
13
+ children: React.ReactNode;
14
+ }
15
+ /**
16
+ * React Context Provider for TesterPayKit SDK.
17
+ *
18
+ * Wraps your app to provide TesterPayKit functionality via the useTesterPayKit hook.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <TesterPayKitProvider
23
+ * apiKey="your-api-key"
24
+ * projectId="project-123"
25
+ * testerId="tester-456"
26
+ * deviceId="device-789"
27
+ * deviceMetadata={{ os: 'Web', osVersion: '1.0', ... }}
28
+ * appMetadata={{ bundleId: 'com.example.app', version: '1.0.0', ... }}
29
+ * >
30
+ * <App />
31
+ * </TesterPayKitProvider>
32
+ * ```
33
+ */
34
+ declare function TesterPayKitProvider({ apiKey, projectId, testerId, deviceId, deviceMetadata, appMetadata, baseURL, children, }: TesterPayKitProviderProps): React.JSX.Element;
35
+ /**
36
+ * React hook for accessing TesterPayKit SDK functionality.
37
+ *
38
+ * Must be used within a TesterPayKitProvider.
39
+ *
40
+ * @returns Object containing tracking methods and SDK instance
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * function MyComponent() {
45
+ * const { trackScreenView, trackTap } = useTesterPayKit();
46
+ *
47
+ * useEffect(() => {
48
+ * trackScreenView('MyComponent');
49
+ * }, []);
50
+ *
51
+ * return (
52
+ * <button onClick={() => trackTap('MyComponent', 'submit-btn')}>
53
+ * Submit
54
+ * </button>
55
+ * );
56
+ * }
57
+ * ```
58
+ */
59
+ declare function useTesterPayKit(): {
60
+ trackScreenView: (screenName: string, metadata?: Record<string, any>) => Promise<void>;
61
+ trackTap: (screenName: string, elementId?: string) => Promise<void>;
62
+ trackEvent: (eventType: string, metadata?: Record<string, any>) => Promise<void>;
63
+ trackError: (message: string, stack?: string) => Promise<void>;
64
+ sdk: TesterPayKit | null;
65
+ };
66
+
67
+ /**
68
+ * TpkBugReportButton — back-compat thin wrapper around TpkBugReportSheet.
69
+ *
70
+ * Renders its own circular FAB that opens/closes a TpkBugReportSheet.
71
+ * Existing consumers and all tests targeting tpk-bug-report-fab /
72
+ * tpk-bug-report-overlay / tpk-bug-* testids keep working unchanged.
73
+ */
74
+
75
+ interface TpkBugReportButtonProps {
76
+ /** Initialised TesterPayKit SDK instance. */
77
+ sdk: TesterPayKit;
78
+ /**
79
+ * Screen / route label passed as `currentScreen` in the report.
80
+ * Falls back to `window.location.pathname` when omitted.
81
+ */
82
+ currentScreen?: string;
83
+ /** Override the button label. Default: "Report a bug". */
84
+ label?: string;
85
+ /** Called after the report is enqueued (localId = outbox UUID). */
86
+ onSubmitted?: (localId: string) => void;
87
+ }
88
+ declare function TpkBugReportButton({ sdk, currentScreen, label, onSubmitted, }: TpkBugReportButtonProps): React.ReactElement;
89
+
90
+ /**
91
+ * TpkBugReportSheet — 4-step wizard for submitting rich bug reports.
92
+ *
93
+ * Step 1 — Typ & Schwere: type chips + severity selector
94
+ * Step 2 — Details: title*, description*, expected/actual behavior, frequency,
95
+ * affected feature + voice mic (prefills title+description)
96
+ * Step 3 — Reproduktion & Beleg: reproduction steps list + screenshot
97
+ * Step 4 — Überprüfen & Senden: read-only summary + submit
98
+ *
99
+ * Back-compat: all data-testid hooks from Phase 1/2 are preserved so
100
+ * existing screenshot + voice + annotate test suites keep passing.
101
+ */
102
+
103
+ interface TpkBugReportSheetProps {
104
+ /** Initialised TesterPayKit SDK instance. */
105
+ sdk: TesterPayKit;
106
+ /** Screen / route label passed as `currentScreen` in the report. */
107
+ currentScreen?: string;
108
+ /** Whether the sheet is currently open. */
109
+ open: boolean;
110
+ /** Called when the user dismisses the sheet (Cancel or backdrop tap). */
111
+ onClose: () => void;
112
+ /** Called after the report is enqueued (localId = outbox UUID). */
113
+ onSubmitted?: (localId: string) => void;
114
+ /**
115
+ * Which step to start on (1-indexed, default 1).
116
+ * Pass 2 to jump directly to the Details step (e.g. after a long-press voice launch).
117
+ */
118
+ initialStep?: number;
119
+ /**
120
+ * When true, automatically begin voice recording on mount at the Details step.
121
+ * The long-press release is a valid user-activation gesture for getUserMedia.
122
+ * If the mic throws / is denied, falls back to the idle mic state (never crashes).
123
+ */
124
+ autoStartVoice?: boolean;
125
+ }
126
+ declare function TpkBugReportSheet({ sdk, currentScreen, open, onClose, onSubmitted, initialStep, autoStartVoice, }: TpkBugReportSheetProps): React.ReactElement | null;
127
+
128
+ /**
129
+ * TesterPayKit brand tokens — single source of truth for the widget set.
130
+ *
131
+ * Usage: import { tpkTheme } from './theme'
132
+ * All widget components consume these values so visual changes only happen here.
133
+ */
134
+ interface TpkColors {
135
+ /** Brand purple — primary buttons, focus rings, AI-related accents */
136
+ primary: string;
137
+ /** Bug-red FAB — stays visible on any background including red error screens */
138
+ bugRed: string;
139
+ /** Card / sheet background */
140
+ surface: string;
141
+ /** Alternate surface — hover states, subtle rows */
142
+ surfaceAlt: string;
143
+ /** Primary text */
144
+ text: string;
145
+ /** Secondary / muted text */
146
+ textMuted: string;
147
+ /** Dividers and input borders */
148
+ border: string;
149
+ /** Success state (confirmation, green check) */
150
+ success: string;
151
+ /** Danger state (delete, critical error label) */
152
+ danger: string;
153
+ /** Dim backdrop when a modal / menu is open */
154
+ backdrop: string;
155
+ }
156
+ interface TpkRadius {
157
+ sm: number;
158
+ md: number;
159
+ lg: number;
160
+ }
161
+ interface TpkZIndex {
162
+ /** Widget overlay — sits above all host-app UI (incl. MUI Drawers, etc.) */
163
+ overlay: number;
164
+ /** Menu popup — above the overlay backdrop but below the FAB itself */
165
+ menu: number;
166
+ /** FAB button — topmost */
167
+ fab: number;
168
+ }
169
+ interface TpkTheme {
170
+ colors: TpkColors;
171
+ radius: TpkRadius;
172
+ shadow: string;
173
+ zIndex: TpkZIndex;
174
+ spacing: (multiplier: number) => number;
175
+ }
176
+ declare const tpkTheme: TpkTheme;
177
+
178
+ /**
179
+ * TesterPayKit i18n — translation dictionary + helpers.
180
+ *
181
+ * Default locale: 'de'. English is additive.
182
+ * makeT(locale) returns a t(key, params?) function.
183
+ * Falls back to 'de' for any key missing in the target locale.
184
+ * Supports simple {param} interpolation.
185
+ */
186
+ type TpkLocale = 'de' | 'en';
187
+ declare const MESSAGES: Record<TpkLocale, Record<string, string>>;
188
+ /**
189
+ * Returns a translation function for the given locale.
190
+ * Falls back to 'de' for any key missing in the target locale.
191
+ * Replaces {param} placeholders with provided param values.
192
+ */
193
+ declare function makeT(locale: TpkLocale): (key: string, params?: Record<string, string | number>) => string;
194
+
195
+ /**
196
+ * TpkTesterHub — branded FAB hub with a popup menu.
197
+ *
198
+ * Renders a fixed bottom-right bug-red circular FAB (white ring so it's
199
+ * visible on any background). Tap opens a card popup menu above the FAB
200
+ * with a dim backdrop. The FAB glyph rotates to ✕ while open.
201
+ *
202
+ * Menu items are defined as data so later phases can add new entries
203
+ * without modifying the render logic. See the TODO below for the
204
+ * planned extension items.
205
+ *
206
+ * TODO — future menu items (Phase 2+):
207
+ * - "📋 Meine Bugs" → opens the tester's submitted-bug list
208
+ * - "🔔 Benachrichtigungen" → opens the in-widget notification feed (with badge)
209
+ * - "📊 Session Details" → earnings + activity summary for the current session
210
+ * - "🔄 My Retests" → retest queue (when assigned retests > 0)
211
+ */
212
+
213
+ interface TpkTesterHubProps {
214
+ /** Initialised TesterPayKit SDK instance. */
215
+ sdk: TesterPayKit;
216
+ /** Current screen / route label forwarded to bug reports. */
217
+ currentScreen?: string;
218
+ /**
219
+ * When provided, an "AI-Tester" menu item is shown. All three sub-fields
220
+ * are forwarded to TpkAiTester.
221
+ */
222
+ aiTester?: {
223
+ apiBaseUrl: string;
224
+ coordinatorToken: string;
225
+ projectId: string;
226
+ };
227
+ /** Locale for all widget copy. Defaults to 'de'. */
228
+ locale?: TpkLocale;
229
+ /** Deep partial theme override. Merged on top of the default tpkTheme. */
230
+ theme?: Partial<TpkTheme>;
231
+ /**
232
+ * Gesture triggers for opening the bug report sheet.
233
+ * Both default to true. Set a key to false to disable that gesture.
234
+ */
235
+ gestures?: {
236
+ threeFingerTap?: boolean;
237
+ shake?: boolean;
238
+ };
239
+ /**
240
+ * When true, renders a floating TpkTaskPanel alongside the hub.
241
+ * Opt-in (default false) because a floating panel is intrusive.
242
+ * The panel shows only when tasks exist.
243
+ */
244
+ taskPanel?: boolean;
245
+ }
246
+ declare function TpkTesterHub({ sdk, currentScreen, aiTester, locale, theme, gestures, taskPanel, }: TpkTesterHubProps): React.ReactElement | null;
247
+
248
+ /**
249
+ * TpkTesterLauncher — the logged-out "springboard" wrapper around TpkTesterHub.
250
+ *
251
+ * On a public surface, NOT every visitor should get a working bug-report hub
252
+ * (a half-working tester widget on a promo is a reputation risk). This widget
253
+ * gates the hub behind a registered tester:
254
+ *
255
+ * logged out → small FAB → panel with three choices:
256
+ * • "Ich möchte Tester werden" → opens the tester signup page
257
+ * • "Ich bin Tester" → magic-link sign-in (email)
258
+ * • "Ausblenden" → hides the widget (persisted)
259
+ * logged in → the full TpkTesterHub (bug report, my bugs, …)
260
+ *
261
+ * TesterPayKit tester auth is PASSWORDLESS (magic-link): the email step asks
262
+ * the server to mail a single-use link that returns to this app's
263
+ * `callbackPath` carrying a token. The callback page calls
264
+ * sdk.consumeTesterMagicLink(token), persists the session, and reloads — at
265
+ * which point this launcher restores it and shows the hub. The token is
266
+ * re-applied AFTER the SDK's anonymous auth settles so the registered identity
267
+ * always wins over the anonymous telemetry one.
268
+ */
269
+
270
+ interface TpkTesterLauncherProps {
271
+ /** Initialised TesterPayKit SDK instance. */
272
+ sdk: TesterPayKit;
273
+ /** Current screen / route label forwarded to the hub. */
274
+ currentScreen?: string;
275
+ /** Locale for all widget copy. Defaults to 'de'. */
276
+ locale?: TpkLocale;
277
+ /** Where "become a tester" sends the user. */
278
+ becomeTesterUrl?: string;
279
+ /** App route that consumes the magic-link token. Default '/tester/callback'. */
280
+ callbackPath?: string;
281
+ theme?: TpkTesterHubProps['theme'];
282
+ aiTester?: TpkTesterHubProps['aiTester'];
283
+ gestures?: TpkTesterHubProps['gestures'];
284
+ }
285
+ declare function TpkTesterLauncher(props: TpkTesterLauncherProps): React.JSX.Element | null;
286
+
287
+ /**
288
+ * TpkMyBugsScreen — slide-in panel showing the tester's submitted bug reports.
289
+ *
290
+ * Controlled via { sdk, open, onClose }. On open, fetches from
291
+ * sdk.getMyBugReports() and renders a branded list with colour-coded
292
+ * status badges. Tapping a row expands its description.
293
+ */
294
+
295
+ interface TpkMyBugsScreenProps {
296
+ sdk: TesterPayKit;
297
+ open: boolean;
298
+ onClose: () => void;
299
+ }
300
+ declare function TpkMyBugsScreen({ sdk, open, onClose }: TpkMyBugsScreenProps): React.ReactElement | null;
301
+
302
+ /**
303
+ * TpkNotificationsScreen — slide-in panel showing the tester's notifications.
304
+ *
305
+ * Controlled via { sdk, open, onClose, onUnreadChange? }. On open, fetches
306
+ * from sdk.getNotifications(). Unread rows are visually distinct. Tapping a
307
+ * row marks it read; "Alle gelesen" marks all.
308
+ */
309
+
310
+ interface TpkNotificationsScreenProps {
311
+ sdk: TesterPayKit;
312
+ open: boolean;
313
+ onClose: () => void;
314
+ /** Called with the new unread count after any read action. */
315
+ onUnreadChange?: (count: number) => void;
316
+ }
317
+ declare function TpkNotificationsScreen({ sdk, open, onClose, onUnreadChange, }: TpkNotificationsScreenProps): React.ReactElement | null;
318
+
319
+ /**
320
+ * TpkGeneralFeedbackDialog — lightweight single-screen feedback dialog.
321
+ *
322
+ * DACH-first copy (German default, English via TpkUiProvider). Provides:
323
+ * - 1–5 star rating
324
+ * - Category select (Allgemein / Feature-Wunsch / Verbesserung / Lob / Kritik)
325
+ * - Title field (max 100 chars)
326
+ * - Details textarea (max 1000 chars)
327
+ *
328
+ * Submits via sdk.submitBugReport({ type: 'generalFeedback', ... })
329
+ * with rating + feedbackCategory in metadata.
330
+ */
331
+
332
+ interface TpkGeneralFeedbackDialogProps {
333
+ sdk: TesterPayKit;
334
+ open: boolean;
335
+ onClose: () => void;
336
+ onSubmitted?: (localId: string) => void;
337
+ /** Current screen / route label forwarded to the report. */
338
+ currentScreen?: string;
339
+ }
340
+ declare function TpkGeneralFeedbackDialog({ sdk, open, onClose, onSubmitted, currentScreen, }: TpkGeneralFeedbackDialogProps): React.ReactElement | null;
341
+
342
+ /**
343
+ * crashCapture — installs global window error handlers and persists the most
344
+ * recent uncaught error to localStorage under the key `tpk:pendingCrash`.
345
+ *
346
+ * SSR-safe: all window/localStorage accesses are guarded.
347
+ * Debounced: a burst of errors within 500 ms is coalesced into one entry.
348
+ */
349
+
350
+ declare const PENDING_CRASH_KEY = "tpk:pendingCrash";
351
+ interface PendingCrash {
352
+ message: string;
353
+ stack: string;
354
+ screen: string;
355
+ ts: number;
356
+ }
357
+ declare function getPendingCrash(): PendingCrash | null;
358
+ declare function clearPendingCrash(): void;
359
+ /**
360
+ * Installs `window.error` and `unhandledrejection` listeners.
361
+ * Returns an uninstaller function.
362
+ *
363
+ * Safe to call in SSR: returns a no-op uninstaller when `window` is absent.
364
+ */
365
+ declare function installCrashCapture(sdk: TesterPayKit): () => void;
366
+
367
+ /**
368
+ * TpkCrashDialog — displays a captured crash and lets the tester submit a report.
369
+ *
370
+ * Controlled: { sdk, crash, open, onClose, onSubmitted? }
371
+ * Red header. Shows error message + expandable stack trace (monospace).
372
+ * Optional "Was hast du gemacht?" notes textarea.
373
+ * Submit → sdk.submitBugReport({ type: 'crash', ... }) then clearPendingCrash().
374
+ * "Verwerfen" closes + clears.
375
+ */
376
+
377
+ interface TpkCrashDialogProps {
378
+ sdk: TesterPayKit;
379
+ crash: PendingCrash | null;
380
+ open: boolean;
381
+ onClose: () => void;
382
+ onSubmitted?: (localId: string) => void;
383
+ }
384
+ declare function TpkCrashDialog({ sdk, crash, open, onClose, onSubmitted, }: TpkCrashDialogProps): React.ReactElement | null;
385
+
386
+ /**
387
+ * TpkSessionEndDialog — quick end-of-session prompt.
388
+ *
389
+ * Controlled: { sdk, open, onClose, onSubmitted? }
390
+ * - 1–5 star rating
391
+ * - Short comment (required)
392
+ * - "Was hast du getestet?" optional field
393
+ *
394
+ * Submit → sdk.submitBugReport({ type: 'generalFeedback', metadata: { rating, kind: 'session_end', tested } })
395
+ * then sdk.endSession('completed') (best-effort).
396
+ */
397
+
398
+ interface TpkSessionEndDialogProps {
399
+ sdk: TesterPayKit;
400
+ open: boolean;
401
+ onClose: () => void;
402
+ onSubmitted?: (localId: string) => void;
403
+ }
404
+ declare function TpkSessionEndDialog({ sdk, open, onClose, onSubmitted, }: TpkSessionEndDialogProps): React.ReactElement | null;
405
+
406
+ /**
407
+ * TpkDebugLogsScreen — slide-in panel showing the SDK debug log ring buffer.
408
+ *
409
+ * Shows the most recent entries newest-first in a monospace list, with a
410
+ * refresh button, a "Leeren" (clear) button, an empty state, and a small
411
+ * info block at the top with sessionId + current earnings.
412
+ */
413
+
414
+ interface TpkDebugLogsScreenProps {
415
+ sdk: TesterPayKit;
416
+ open: boolean;
417
+ onClose: () => void;
418
+ }
419
+ declare function TpkDebugLogsScreen({ sdk, open, onClose, }: TpkDebugLogsScreenProps): React.ReactElement | null;
420
+
421
+ /**
422
+ * TpkWorkboardScreen — slide-in panel showing the tester's assigned tasks
423
+ * plus the current build info.
424
+ *
425
+ * Controlled via { sdk, open, onClose }. On open, fetches sdk.getMyTasks()
426
+ * and optionally sdk.getMyPoints() (best-effort). Each task row has a
427
+ * "Starten" / "Erledigt" / ✓ action depending on its status.
428
+ */
429
+
430
+ interface TpkWorkboardScreenProps {
431
+ sdk: TesterPayKit;
432
+ open: boolean;
433
+ onClose: () => void;
434
+ }
435
+ declare function TpkWorkboardScreen({ sdk, open, onClose }: TpkWorkboardScreenProps): React.ReactElement | null;
436
+
437
+ /**
438
+ * TpkRetestScreen — slide-in panel showing the tester's retest queue.
439
+ *
440
+ * Controlled via { sdk, open, onClose }. On open, fetches sdk.getMyRetests().
441
+ * Each row shows bug title + status badge + fix info if present. Two action
442
+ * buttons: "Bestätigt (behoben)" → sdk.verifyRetest(bugId) and
443
+ * "Wieder offen" → sdk.reopenRetest(bugId). Re-fetches after each action.
444
+ *
445
+ * A bug with acceptance steps is judged step by step first: "Bestätigt"
446
+ * stays disabled until every step passed, and the verdicts go along with
447
+ * verify / reopen.
448
+ */
449
+
450
+ interface TpkRetestScreenProps {
451
+ sdk: TesterPayKit;
452
+ open: boolean;
453
+ onClose: () => void;
454
+ }
455
+ declare function TpkRetestScreen({ sdk, open, onClose }: TpkRetestScreenProps): React.ReactElement | null;
456
+
457
+ /**
458
+ * TpkSessionDetailsScreen — slide-in panel showing live session info.
459
+ *
460
+ * Pure local/live: no network calls needed. Shows session id, started-at,
461
+ * elapsed time, live earnings, and debug log count. A refresh button
462
+ * re-reads the live values from the SDK.
463
+ */
464
+
465
+ interface TpkSessionDetailsScreenProps {
466
+ sdk: TesterPayKit;
467
+ open: boolean;
468
+ onClose: () => void;
469
+ }
470
+ declare function TpkSessionDetailsScreen({ sdk, open, onClose }: TpkSessionDetailsScreenProps): React.ReactElement | null;
471
+
472
+ /**
473
+ * TpkTaskPanel — floating, draggable checklist panel (Flutter parity).
474
+ *
475
+ * Renders NOTHING when getMyTasks() returns an empty list.
476
+ * When tasks exist: a fixed, draggable card (collapsed by default) showing
477
+ * "Aufgaben X/Y" + a progress bar. Expanding reveals task rows with
478
+ * "Starten" / "Erledigt" actions. Can be dismissed for the session.
479
+ *
480
+ * z-index is intentionally below the FAB menu so it doesn't overlap controls.
481
+ * Position is fixed, bottom-left (so it doesn't clash with bottom-right FAB).
482
+ */
483
+
484
+ interface TpkTaskPanelProps {
485
+ sdk: TesterPayKit;
486
+ /** Called when user dismisses the panel. */
487
+ onClose?: () => void;
488
+ /** Called after a task action so the parent can refresh its badge. */
489
+ onTasksChanged?: () => void;
490
+ }
491
+ declare function TpkTaskPanel({ sdk, onClose, onTasksChanged }: TpkTaskPanelProps): React.ReactElement | null;
492
+
493
+ /**
494
+ * gestures.ts — lightweight gesture helpers for the TesterPayKit widget.
495
+ *
496
+ * All functions are SSR-safe (no-op when document/window is absent).
497
+ * Both installers return an uninstall function.
498
+ */
499
+ /**
500
+ * Installs a document-level touchstart listener.
501
+ * When exactly 3 simultaneous touch points are detected, calls onTrigger.
502
+ * Debounced so rapid re-fires count as one gesture (300 ms window).
503
+ *
504
+ * @returns Uninstaller — call it to remove the listener.
505
+ */
506
+ declare function installThreeFingerTap(onTrigger: () => void): () => void;
507
+ interface ShakeDetectionOptions {
508
+ /** Acceleration delta threshold (m/s²) that counts as a shake. Default: 15. */
509
+ threshold?: number;
510
+ /** Minimum ms between successive triggers. Default: 1500. */
511
+ cooldownMs?: number;
512
+ }
513
+ /**
514
+ * Requests iOS DeviceMotion permission (needed on Safari iOS 13+).
515
+ * Must be called from a user-gesture handler.
516
+ *
517
+ * Returns:
518
+ * - true if permission granted or not required (Android / desktop)
519
+ * - false if permission denied or DeviceMotionEvent unavailable
520
+ */
521
+ declare function requestShakePermission(): Promise<boolean>;
522
+ /**
523
+ * Installs a window-level 'devicemotion' listener.
524
+ * When the device is shaken (acceleration delta > threshold), calls onTrigger.
525
+ * Respects a cooldown between triggers.
526
+ *
527
+ * iOS guard: if DeviceMotionEvent.requestPermission exists, this function does
528
+ * NOT call it automatically (it needs a user gesture). Attach the listener
529
+ * immediately on non-iOS; on iOS the listener fires only after the caller
530
+ * obtains permission via requestShakePermission() — which is fine because
531
+ * the native OS only delivers events after permission is granted.
532
+ *
533
+ * No-op + never throws when DeviceMotionEvent is absent (e.g. jsdom).
534
+ *
535
+ * @returns Uninstaller — call it to remove the listener.
536
+ */
537
+ declare function installShakeDetection(onTrigger: () => void, opts?: ShakeDetectionOptions): () => void;
538
+
539
+ /**
540
+ * Web screenshot capture utility using html2canvas.
541
+ *
542
+ * Renders the current DOM to a canvas and returns a JPEG dataURL.
543
+ * Browser-only — returns null and logs a warning in non-browser environments
544
+ * or when html2canvas fails.
545
+ *
546
+ * Contract: Part B — web screenshot capture for TpkBugReportButton
547
+ *
548
+ * Auto-PII masking: any element carrying `data-tpk-sensitive` in the DOM
549
+ * (placed by <TpkSensitive> or by hand) is masked in the cloned document
550
+ * before rendering — nothing is sent to the canvas unredacted.
551
+ */
552
+ /**
553
+ * Finds every `[data-tpk-sensitive]` element under `root`, applies the
554
+ * requested mask strategy, and returns the number of elements masked.
555
+ *
556
+ * Pure helper — operates on any object with `querySelectorAll`.
557
+ * Does NOT depend on html2canvas or a real browser: safe to unit-test in jsdom.
558
+ *
559
+ * @param root Any DOM subtree root (Document, Element, or jsdom equivalent).
560
+ * @returns Number of elements that were masked.
561
+ */
562
+ declare function applySensitiveMasks(root: {
563
+ querySelectorAll: (sel: string) => ArrayLike<Element>;
564
+ }): number;
565
+ /**
566
+ * Capture the current page as a JPEG dataURL.
567
+ *
568
+ * @returns A JPEG dataURL string, or null if capture failed or unavailable.
569
+ */
570
+ declare function captureScreenshot(): Promise<string | null>;
571
+
572
+ /**
573
+ * TpkSensitive — auto-PII masking marker for screenshots.
574
+ *
575
+ * Wrap any React element in <TpkSensitive> and it will be automatically
576
+ * blurred or blocked in every screenshot captured by captureScreenshot().
577
+ *
578
+ * @example
579
+ * <TpkSensitive>
580
+ * <input type="password" ... />
581
+ * </TpkSensitive>
582
+ *
583
+ * // block mode (opaque rectangle):
584
+ * <TpkSensitive mask="block">
585
+ * <span>{cardNumber}</span>
586
+ * </TpkSensitive>
587
+ *
588
+ * @remarks
589
+ * Non-React / third-party markup: add the attribute directly on any element:
590
+ * <div data-tpk-sensitive data-tpk-mask="block">...</div>
591
+ * captureScreenshot will mask those elements the same way.
592
+ */
593
+
594
+ type TpkSensitiveMask = 'blur' | 'block';
595
+ interface TpkSensitiveProps {
596
+ /** Masking strategy. 'blur' (default) applies a 12 px gaussian blur.
597
+ * 'block' replaces the element with a solid opaque rectangle. */
598
+ mask?: TpkSensitiveMask;
599
+ children?: React.ReactNode;
600
+ }
601
+ /**
602
+ * Marker component. Renders a plain <span> with `data-tpk-sensitive`
603
+ * (and `data-tpk-mask`) so captureScreenshot can locate and mask it.
604
+ * Zero visual side-effect in normal rendering.
605
+ */
606
+ declare function TpkSensitive({ mask, children }: TpkSensitiveProps): React.ReactElement;
607
+
608
+ /**
609
+ * Browser MediaRecorder helper for voice bug capture.
610
+ *
611
+ * Usage:
612
+ * const session = await startVoiceRecording();
613
+ * if (session.unsupported) { ... }
614
+ * // user records …
615
+ * const result = await session.stop();
616
+ * if (result) { sdk.extractBugFromVoice(result.base64, result.mimeType); }
617
+ */
618
+ interface VoiceRecordingResult {
619
+ /** Raw base64 audio (no data: prefix). */
620
+ base64: string;
621
+ /** MIME type reported by MediaRecorder (e.g. 'audio/webm;codecs=opus'). */
622
+ mimeType: string;
623
+ /** Recording duration in milliseconds. */
624
+ durationMs: number;
625
+ }
626
+ interface VoiceRecordingSession {
627
+ /** Stop recording and resolve the result. Returns null if recording produced no data. */
628
+ stop: () => Promise<VoiceRecordingResult | null>;
629
+ }
630
+ interface VoiceRecordingUnsupported {
631
+ unsupported: true;
632
+ reason: string;
633
+ }
634
+ /**
635
+ * Start a voice recording session using the browser's MediaRecorder API.
636
+ *
637
+ * Returns a `VoiceRecordingUnsupported` discriminant when the environment
638
+ * does not support audio capture (SSR, no mic permission API, etc.) —
639
+ * it never throws.
640
+ */
641
+ declare function startVoiceRecording(): Promise<VoiceRecordingSession | VoiceRecordingUnsupported>;
642
+
643
+ /**
644
+ * Browser screen recording helper using the Screen Capture API.
645
+ *
646
+ * Usage:
647
+ * const result = await startScreenRecording();
648
+ * if (result === 'unsupported') { ... }
649
+ * if (result === 'cancelled') { ... }
650
+ * const { stop } = result;
651
+ * // user clicks stop later…
652
+ * const recording = await stop(); // null if no data
653
+ */
654
+ interface ScreenRecordingResult {
655
+ /** Raw base64 video data (no data: prefix). */
656
+ base64: string;
657
+ /** MIME type reported by MediaRecorder (e.g. 'video/webm'). */
658
+ mimeType: string;
659
+ /** Recording duration in milliseconds. */
660
+ durationMs: number;
661
+ /** Approximate byte size of the raw recording. */
662
+ sizeBytes: number;
663
+ }
664
+ interface ScreenRecordingSession {
665
+ /** Stop recording and resolve the result. Returns null if no data was produced. */
666
+ stop: () => Promise<ScreenRecordingResult | null>;
667
+ }
668
+ interface ScreenRecordingUnsupported {
669
+ unsupported: true;
670
+ reason: string;
671
+ }
672
+ /**
673
+ * Start a screen recording session using the browser's Screen Capture API.
674
+ *
675
+ * Returns:
676
+ * - `{ unsupported: true, reason }` when the environment does not support
677
+ * screen capture (SSR, old browser, no MediaRecorder).
678
+ * - `'cancelled'` when the user dismissed the OS screen-picker
679
+ * (getDisplayMedia rejects with NotAllowedError / name: 'NotAllowedError').
680
+ * - A `ScreenRecordingSession` with a `stop()` method when recording is underway.
681
+ *
682
+ * Never throws.
683
+ */
684
+ declare function startScreenRecording(): Promise<ScreenRecordingSession | ScreenRecordingUnsupported | 'cancelled'>;
685
+
686
+ interface TpkAiTesterProps {
687
+ visible: boolean;
688
+ /** API base URL — same value the rest of the SDK uses for HTTP. */
689
+ apiBaseUrl: string;
690
+ /** Coordinator JWT — used for the initial POST to start the session. */
691
+ coordinatorToken: string;
692
+ /** Project UUID. */
693
+ projectId: string;
694
+ /** Configuration sent to the create-session endpoint. */
695
+ configuration?: {
696
+ durationMinutes?: number;
697
+ maxActions?: number;
698
+ strategy?: 'llm' | 'random' | 'hybrid';
699
+ focusAreas?: string[];
700
+ captureScreenshots?: boolean;
701
+ submitBugs?: boolean;
702
+ };
703
+ /** Called when the user clicks Stop or the session ends naturally. */
704
+ onClose: () => void;
705
+ /** Called once on session end with the final status payload. */
706
+ onSessionEnded?: (status: 'completed' | 'failed' | 'cancelled', sessionId: string | null) => void;
707
+ }
708
+ declare function TpkAiTester({ visible, apiBaseUrl, coordinatorToken, projectId, configuration, onClose, onSessionEnded, }: TpkAiTesterProps): React.ReactElement | null;
709
+
710
+ /**
711
+ * ScreenshotAnnotator — full-screen modal canvas editor for annotating
712
+ * screenshots before attaching them to a bug report.
713
+ *
714
+ * Tools: Pen · Arrow · Rectangle · Text · Blur/Pixelate (PII-redaction)
715
+ * Features: Undo / Redo · Color swatches + native color picker · Stroke width
716
+ *
717
+ * SSR-safe: all canvas/window access is guarded behind typeof window checks
718
+ * and null-context guards.
719
+ */
720
+
721
+ interface ScreenshotAnnotatorProps {
722
+ /** Source screenshot as a data URL */
723
+ dataUrl: string;
724
+ /** Called with the annotated image data URL when the user saves */
725
+ onSave: (annotatedDataUrl: string) => void;
726
+ /** Called when the user cancels without saving */
727
+ onCancel: () => void;
728
+ }
729
+ declare function ScreenshotAnnotator({ dataUrl, onSave, onCancel, }: ScreenshotAnnotatorProps): React.ReactElement | null;
730
+
731
+ /**
732
+ * Pure (DOM-free) operation stack for the screenshot annotation editor.
733
+ *
734
+ * All types, the undo/redo stack, arrowhead geometry, and pixelate-region
735
+ * math live here so they can be unit-tested without a canvas.
736
+ */
737
+ type ToolKind = 'pen' | 'arrow' | 'rect' | 'text' | 'blur';
738
+ interface Point {
739
+ x: number;
740
+ y: number;
741
+ }
742
+ interface StrokeStyle {
743
+ color: string;
744
+ width: number;
745
+ }
746
+ interface PenOp {
747
+ kind: 'pen';
748
+ points: Point[];
749
+ style: StrokeStyle;
750
+ }
751
+ interface ArrowOp {
752
+ kind: 'arrow';
753
+ from: Point;
754
+ to: Point;
755
+ style: StrokeStyle;
756
+ }
757
+ interface RectOp {
758
+ kind: 'rect';
759
+ from: Point;
760
+ to: Point;
761
+ style: StrokeStyle;
762
+ }
763
+ interface TextOp {
764
+ kind: 'text';
765
+ position: Point;
766
+ text: string;
767
+ style: StrokeStyle;
768
+ fontSize: number;
769
+ }
770
+ interface BlurOp {
771
+ kind: 'blur';
772
+ from: Point;
773
+ to: Point;
774
+ /** Pixel size for the pixelation block (default 12) */
775
+ blockSize: number;
776
+ }
777
+ type AnnotateOp = PenOp | ArrowOp | RectOp | TextOp | BlurOp;
778
+ interface OpStack {
779
+ ops: AnnotateOp[];
780
+ /** Operations that were undone (redo buffer) */
781
+ redoBuffer: AnnotateOp[];
782
+ }
783
+ declare function emptyStack(): OpStack;
784
+ declare function pushOp(stack: OpStack, op: AnnotateOp): OpStack;
785
+ declare function undo(stack: OpStack): OpStack;
786
+ declare function redo(stack: OpStack): OpStack;
787
+ /**
788
+ * Returns the two wing points for an arrowhead at `to` pointing from `from`.
789
+ * Returned as [wing1, wing2] in the same coordinate space.
790
+ */
791
+ declare function arrowheadPoints(from: Point, to: Point, headLength?: number, headAngle?: number): [Point, Point];
792
+ /**
793
+ * Normalises a rect defined by two arbitrary corner points into a
794
+ * { x, y, width, height } with width and height always positive.
795
+ */
796
+ declare function normaliseRect(from: Point, to: Point): {
797
+ x: number;
798
+ y: number;
799
+ width: number;
800
+ height: number;
801
+ };
802
+ /**
803
+ * Returns the list of pixel-block cells for a pixelate pass.
804
+ * Each cell is { x, y, width, height } — the grid of blocks that cover
805
+ * the region. Cells on the right/bottom edges may be narrower/shorter.
806
+ *
807
+ * This is pure math; the actual pixel-sampling runs on the canvas.
808
+ */
809
+ declare function pixelateBlocks(from: Point, to: Point, blockSize: number): Array<{
810
+ x: number;
811
+ y: number;
812
+ width: number;
813
+ height: number;
814
+ }>;
815
+
816
+ /**
817
+ * TpkUiContext — internal context carrying locale + theme for all TPK widgets.
818
+ *
819
+ * TpkUiProvider({ locale?, theme?, children })
820
+ * - locale defaults to the browser language (de* → German, else English)
821
+ * - theme = deepMerge(tpkTheme, themeOverride)
822
+ * - exposes useTpkUi() hook
823
+ *
824
+ * Components work WITHOUT a provider: useTpkUi() returns 'de' + default theme.
825
+ * This ensures existing tests that render components without a provider keep passing.
826
+ */
827
+
828
+ type TFunc = (key: string, params?: Record<string, string | number>) => string;
829
+ interface TpkUiContextValue {
830
+ t: TFunc;
831
+ locale: TpkLocale;
832
+ theme: TpkTheme;
833
+ }
834
+ interface TpkUiProviderProps {
835
+ locale?: TpkLocale;
836
+ theme?: Partial<TpkTheme>;
837
+ children: React.ReactNode;
838
+ }
839
+ declare function TpkUiProvider({ locale, theme, children }: TpkUiProviderProps): React.ReactElement;
840
+ /**
841
+ * Returns { t, locale, theme } from the nearest TpkUiProvider.
842
+ * Falls back to 'de' + default theme if no provider is present.
843
+ */
844
+ declare function useTpkUi(): TpkUiContextValue;
845
+
846
+ /**
847
+ * TpkWidgetTour — first-launch onboarding shown ONCE.
848
+ *
849
+ * 3 swipeable cards (brand purple):
850
+ * Card 1 — "Dein Tester-Knopf" (the FAB)
851
+ * Card 2 — "Sprich den Bug" (voice — hero card)
852
+ * Card 3 — "Screenshot inklusive" (capture + annotate)
853
+ *
854
+ * Gated by localStorage key `tpk:tourSeen`.
855
+ * Use hasSeenTour() / markTourSeen() from this module.
856
+ *
857
+ * Props:
858
+ * onDone — called when the user finishes ("Los geht's") or skips.
859
+ * onSkip — optional alias; if provided, called on skip instead of onDone.
860
+ * onDone is still called after markTourSeen on both paths.
861
+ */
862
+
863
+ /** SSR-safe: returns false if localStorage is unavailable. */
864
+ declare function hasSeenTour(): boolean;
865
+ /** SSR-safe: best-effort write. */
866
+ declare function markTourSeen(): void;
867
+ interface TpkWidgetTourProps {
868
+ /** Called after the tour finishes (last card) OR is skipped — in both cases markTourSeen() is called first. */
869
+ onDone: () => void;
870
+ /** Optional separate callback on skip. onDone is still called. */
871
+ onSkip?: () => void;
872
+ }
873
+ declare function TpkWidgetTour({ onDone, onSkip }: TpkWidgetTourProps): React.ReactElement;
874
+
875
+ export { MESSAGES, PENDING_CRASH_KEY, ScreenshotAnnotator, TesterPayKitProvider, TpkAiTester, TpkBugReportButton, TpkBugReportSheet, TpkCrashDialog, TpkDebugLogsScreen, TpkGeneralFeedbackDialog, TpkMyBugsScreen, TpkNotificationsScreen, TpkRetestScreen, TpkSensitive, TpkSessionDetailsScreen, TpkSessionEndDialog, TpkTaskPanel, TpkTesterHub, TpkTesterLauncher, TpkUiProvider, TpkWidgetTour, TpkWorkboardScreen, applySensitiveMasks, arrowheadPoints, captureScreenshot, clearPendingCrash, emptyStack, getPendingCrash, hasSeenTour, installCrashCapture, installShakeDetection, installThreeFingerTap, makeT, markTourSeen, normaliseRect, pixelateBlocks, pushOp, redo, requestShakePermission, startScreenRecording, startVoiceRecording, tpkTheme, undo, useTesterPayKit, useTpkUi };
876
+ export type { AnnotateOp, ArrowOp, BlurOp, OpStack, PenOp, PendingCrash, Point, RectOp, ScreenRecordingResult, ScreenRecordingSession, ScreenRecordingUnsupported, ScreenshotAnnotatorProps, ShakeDetectionOptions, StrokeStyle, TesterPayKitProviderProps, TextOp, ToolKind, TpkAiTesterProps, TpkBugReportButtonProps, TpkBugReportSheetProps, TpkColors, TpkCrashDialogProps, TpkDebugLogsScreenProps, TpkGeneralFeedbackDialogProps, TpkLocale, TpkMyBugsScreenProps, TpkNotificationsScreenProps, TpkRadius, TpkRetestScreenProps, TpkSensitiveMask, TpkSensitiveProps, TpkSessionDetailsScreenProps, TpkSessionEndDialogProps, TpkTaskPanelProps, TpkTesterHubProps, TpkTesterLauncherProps, TpkTheme, TpkWorkboardScreenProps, TpkZIndex, VoiceRecordingResult, VoiceRecordingSession, VoiceRecordingUnsupported };