/** * Public API + shared runtime. ESM entry (NO auto-init). * * import { init, destroy } from "tyrekick"; * * Everything the widget renders lives inside ONE shadow root on a single host * element appended to . We never mutate host-page DOM or styles. */ import type { TyrekickConfig, FeedbackPayload, Position, Transport } from "./types"; export interface Resolved { webhook: string; appVersion: string; projectName: string; position: Position; accent: string; theme: "auto" | "light" | "dark"; branding: boolean; fieldName: boolean; transport: Transport; persist: boolean; captureErrors: boolean; /** Shared-review key, or null when each reviewer sees only their own pins. */ reviewKey: string | null; } export interface Pin { /** Stable identity for this comment/pin, regardless of UI reordering. */ id: string; /** User-facing display number for top-level pins; replies derive from roots. */ n: number; /** * The number the destination assigned this comment, once it has one. It is a * position in the page's whole history — gaps and all — so it never changes * when another comment is declined, unlike a count taken over what happens to * be on screen. Null while a comment is local-only or the destination does * not number (Discord, an older worker); those fall back to counting. */ serverN: number | null; /** document-space coordinates (survive scroll) */ docX: number; docY: number; /** viewport coordinates at creation (used to place the composer) */ clientX: number; clientY: number; status: "pending" | "sent" | "failed"; /** Root pin id this comment replies to, or null for a top-level comment. */ replyToId: string | null; /** * Click position as a fraction of the anchored element's box (0..1), or null * when there is no element anchor. Lets pins re-attach to their element after * reload/resize instead of trusting stale document coordinates. */ fx: number | null; fy: number | null; /** * The payload id of the DELIVERED comment (uuid; differs from Pin.id — a new * payload id is minted per send attempt). It is the capability used to look * up fix-status on the worker's /receipts route. Null until a send succeeds; * meaningless on the Discord transport (write-only, no read-back). */ deliveredId: string | null; /** Closure state pulled back from the worker, or null while open/unknown. */ receipt: { status: "resolved" | "declined"; note: string | null; at: string | null; } | null; /** * A one-sentence AI acknowledgement of this comment, pulled back from the * worker's /receipts route, or null while none. Runtime-only exactly like * `receipt`: fetched every load, NEVER persisted to localStorage. It is a * thread message, not a status change — it never touches `receipt` or the * pin's open/closed state. */ aiReply: string | null; /** * True when this pin is SOMEONE ELSE's comment, pulled from the worker's * /shared view (reviewKey set). Foreign pins are strictly read-only: they * are never persisted to localStorage, never retried or discarded, and never * counted as this reviewer's unsent work. They exist so a reviewer can see * what has already been said before saying it again. */ foreign: boolean; anchor: FeedbackPayload["anchor"]; /** comment text as submitted (empty while pending) */ body: string; reviewer: string | null; /** ISO timestamp of submission ("" while pending) */ at: string; el: HTMLElement | null; } export interface Overlay { enter(): void; exit(): void; layout(): void; captureOn(): void; captureOff(): void; /** Create a pending pin; pass `replyToId` (a root pin's stable id) for a * marker-less reply (used by drawer follow-ups). */ addPin(docX: number, docY: number, anchor: Pin["anchor"], replyToId?: string | null): Pin; removePin(p: Pin): void; /** * Reconcile pin presentation with runtime state: pins render whenever any * exist (unless the reviewer hid them), dimmed when the widget is idle, with * scroll/resize tracking owned here so visible pins can never drift. */ syncPins(): void; /** Reviewer's "hide pins" preference (drawer eye toggle); comment mode overrides it. */ setPinsHidden(hidden: boolean): void; pinsHidden(): boolean; isActive(): boolean; focus(): void; destroy(): void; } export interface Drawer { open(): void; close(): void; isOpen(): boolean; /** Open the thread popover for a pin's comment, anchored at the pin. */ openThread(p: Pin): void; refresh(): void; destroy(): void; } export interface Panel { open(p: Pin, prefill?: string): void; close(restoreFocus: boolean): void; isOpen(): boolean; /** If the composer is open but untouched, discard its pending pin and close; returns whether it did. */ abandonClean(): boolean; /** Close like Esc: abandon a pending pin but KEEP the saved draft text. */ dismiss(restoreFocus: boolean): void; destroy(): void; } export interface Draft { body: string; name: string; } export interface Runtime { cfg: Resolved; root: ShadowRoot; host: HTMLElement; sessionId: string; pins: Pin[]; overlay: Overlay; panel: Panel; drawer: Drawer; trigger: HTMLButtonElement; pendingDraft: Draft | null; savePins(): void; saveDraft(d: Draft): void; clearDraft(): void; /** Persist delivered-comment receipts (worker transport only, persist-gated). */ saveReceipts(): void; /** Ask the worker for fix-status on restored delivered comments (throttled, silent). */ checkReceipts(): void; /** * Pull every reviewer's pins from the worker's /shared view (silent, and * throttled unless `force` — see SHARED_POLL_MIN_MS). */ fetchShared(force?: boolean): void; } export declare function init(config: TyrekickConfig): void; export declare function destroy(): void;