/** * Zero-code SEMANTIC UI capture for the Web. * * Listens for taps/clicks and resolves the ACCESSIBLE LABEL of the interactive * element the user hit — aria-label, visible text, title, alt, placeholder, name * — so a tap is captured as `tapped "Buy"`, not raw coordinates. This is the * "what the user did" half that fuses with network capture ("what happened"). * * Also owns the other behavioral producers, all behind one enable gate: * - screen_view via the History API (pushState/replaceState patch + popstate/ * hashchange + initial load), tracking prev_screen; * - crash via window 'error' + 'unhandledrejection' — observe only, never * preventDefault; type + first stack frame only (messages can carry PII). * * The label is read from the DOM the OS/framework already computed for * accessibility — no screenshot, no model, sub-millisecond. Fail-open by design: * a capture error never propagates into the app. PII in any field is redacted by * the sink before delivery (same path as network bodies). */ export interface UiEvent { type: 'tap' | 'rage_tap' | 'dead_tap'; /** Accessible name of the tapped interactive element (redacted by the sink). */ label: string; /** ARIA/DOM role (button, link, tab, textbox…). */ role: string; /** Current screen/route: pathname + hash (or the configured router hook). */ screen: string; /** Normalized 0.0–1.0 fraction of the viewport (contract shape). */ x: number; y: number; timestampMs: number; /** Taps in the burst — rage_tap only. */ count?: number; } export interface ScrollUiEvent { type: 'possible_rage_scroll' | 'dead_scroll'; screen: string; /** Normalized 0.0–1.0 fraction of the viewport. */ x: number; y: number; timestampMs: number; /** Direction reversals in the burst — possible_rage_scroll only. */ count?: number; } export interface ScreenViewEvent { type: 'screen_view'; /** The screen now shown. */ screen: string; /** The screen left ('' on initial load). */ prevScreen: string; timestampMs: number; } export interface CrashEvent { type: 'crash'; /** The error's name/constructor (TypeError, RangeError, …). */ errorKind: string; /** First stack FRAME only — never the message (messages carry PII). */ frame: string; screen: string; timestampMs: number; } export interface ScrollBurstState { /** ms of the last scroll tick (any flurry event). */ lastTickMs: number; /** ms of the last gesture START (a tick after a ≥GAP quiet period). */ lastGestureMs: number; /** direction of the last gesture (null = none in the current window). */ lastGestureDown: boolean | null; /** direction reversals across gestures in the current window. */ reversals: number; reported: boolean; } export declare const freshScrollBurst: () => ScrollBurstState; /** Fold a scroll tick (its [directionDown]) into the burst. A new GESTURE begins when * ≥GAP since the last tick; ≥MIN_REVERSALS direction reversals across gestures within * WINDOW = thrashing (scrubbing up/down) → report the reversal count ONCE. Steady * one-direction scrolling never fires — that's browsing, not rage. Mirrors the Android * RageScrollDetector. Pure. (-1 = unset sentinel, so a legit ts of 0 isn't "no prior".) */ export declare function updateScrollBurst(s: ScrollBurstState, now: number, directionDown: boolean): { state: ScrollBurstState; rage: number | null; }; /** True when the container can move NEITHER way — a genuinely frozen/non-scrollable * region, NOT merely a list scrolled to its end (that's normal overscroll and * flagging it floods the signal). A REAL determination from measured geometry, * never a guess. Pure. */ export declare function isDeadScroll(scrollTop: number, clientHeight: number, scrollHeight: number): boolean; export interface BurstState { x: number; y: number; lastMs: number; count: number; reported: boolean; label: string; } export declare const freshBurst: () => BurstState; /** Fold a tap into the rage burst. Returns the next state and, when a burst * first crosses the threshold, the tap count to report (once per burst). * Matches Flutter: the window slides (position/time update every tap) and the * label is part of the burst identity. Pure — exported for tests. */ export declare function updateBurst(s: BurstState, x: number, y: number, now: number, label?: string): { state: BurstState; rage: number | null; }; /** Viewport-normalize a CSS-pixel point to the contract's 0.0–1.0 shape. * Mirrors Flutter's _normalize: unknown viewport → (0,0). Pure — for tests. */ export declare function normalizePoint(px: number, py: number, vw?: number, vh?: number): { x: number; y: number; }; /** The error's name/constructor — the type only, never the message. Exported for tests. */ export declare function errorKindOf(v: unknown): string; /** First stack FRAME (`at fn (file:1:2)` / `fn@file:1:2`) — skips the leading * `Name: message` line V8 prepends, because messages can carry PII. Exported for tests. */ export declare function firstFrame(stack: unknown): string; export interface BehaviorOptions { onTap: (e: UiEvent) => void; onScroll?: (e: ScrollUiEvent) => void; /** Fired on every wheel scroll (keeps the session alive on a pure-scroll read). */ onScrollActivity?: () => void; onScreenView?: (e: ScreenViewEvent) => void; onCrash?: (e: CrashEvent) => void; /** Route-name override (router hook). Default: location.pathname + hash. */ screenName?: () => string; /** Install paused (consent gate decided before install) — the initial * screen_view must not slip past an opt-out. Default true. */ startEnabled?: boolean; } export interface ElementLike { tagName: string; getAttribute(name: string): string | null; innerText?: string; parentElement: ElementLike | null; onclick?: unknown; } /** Walk up to the nearest interactive element and read its accessible name — * exactly what a screen reader would announce. Exported for tests. */ export declare function resolveTarget(el: ElementLike | null): { label: string; role: string; } | null; export declare function setBehaviorEnabled(v: boolean): void; export declare function isBehaviorInstalled(): boolean; /** The current screen/route — for the lifecycle (app_foreground/app_background) * markers emitted from index.ts, so they carry the screen the user was on. */ export declare function getCurrentScreen(): string; export declare function installBehaviorCapture(o: BehaviorOptions): void; export declare function uninstallBehaviorCapture(): void;