/**
* Tyrekick — public types. This file is the contract every module and
* template builds against. Do not change field names without a schema bump.
*/
export type Position = "bottom-right" | "bottom-left";
export type Transport = "json" | "discord";
export interface TyrekickConfig {
/** Destination URL that receives the POST. Required. */
webhook: string;
/** Version string of the prototype under review. Required. */
appVersion: string;
/** Human label for the project. Default: document.title */
projectName?: string;
/** Trigger button corner. Default: "bottom-right" */
position?: Position;
/** Trigger + pin colour. Default: "#FFC53D" */
accent?: string;
/**
* Widget colour scheme. "auto" (default) follows the visitor's
* prefers-color-scheme at init and updates live when it changes;
* "light" / "dark" pin it. Host-page styles are never touched.
*/
theme?: "auto" | "light" | "dark";
/** Show the "Built by Frontier Operations" footer. Default: true */
branding?: boolean;
/** Optional reviewer-name input. Default: { name: true } */
fields?: { name?: boolean };
/**
* How the payload is delivered.
* - "json" (default): POST the raw JSON payload (§ Payload). For a
* same-origin function, a CORS-enabled endpoint, or a form
* backend. Success = HTTP 2xx and, if a body is returned,
* it is not `{"ok":false}`.
* - "discord": map the payload to a Discord webhook message
* ({ content }) and POST that. Success = HTTP 2xx (Discord
* returns 204). Cross-origin friendly.
*/
transport?: Transport;
/**
* Use localStorage for draft recovery (restore unsent text after a failed
* submit) and to keep this session's pins across reloads. Default: true.
* No storage keys are read/written when false.
*/
persist?: boolean;
/**
* Record the page's uncaught errors / unhandled rejections (via window
* "error" and "unhandledrejection" listeners — console is never patched)
* and attach the last few to each payload as `page_errors`. Default: true.
* Input VALUES are never captured anywhere, regardless of this flag.
*/
captureErrors?: boolean;
/**
* SHARED REVIEW. Setting this turns a review from N private conversations
* into one shared page: every reviewer sees every other reviewer's pins
* (read-only) alongside their own. Unset (the default) = each reviewer sees
* only their own pins, which is Tyrekick's historical behaviour.
*
* The value must match `TYREKICK_REVIEW_KEY` on your Worker; the Worker's
* `/shared` route stays disabled until that secret is set. Worker transport
* (`transport: "json"`) only — Discord is write-only and cannot read back.
*
* Understand the trade before switching it on: this key ships inside your
* page, so **anyone who can open the prototype can read every comment on
* it, including reviewer names**. That is the right trade for a private
* link shared with people you trust, and the wrong one for a public URL.
* Rotate the Worker secret to revoke access.
*/
reviewKey?: string;
}
/**
* Payload schema v2 — do not extend without bumping `schema`.
* v2 adds the source-mapping layer so a coding agent can act on feedback:
* element identity/text (greppable in source), structural context (nearest
* heading + landmark), richer env, and recent page errors.
*/
export interface FeedbackPayload {
schema: 2;
/** crypto.randomUUID() per comment */
id: string;
/** ISO-8601 with timezone */
created_at: string;
project_name: string;
app_version: string;
/** location.pathname + search + hash */
route: string;
/** location.href */
url: string;
/** comment text, trimmed */
body: string;
reviewer_name: string | null;
/** crypto.randomUUID(), generated once per page load */
session_id: string;
anchor: {
/** percentage of document width at click time, 1 decimal */
x_pct: number;
/** percentage of document height at click time, 1 decimal */
y_pct: number;
/** best-effort CSS selector of the deepest HOST element at the point,
* max 5 segments, or null. Must never be the widget's own nodes. */
selector: string | null;
viewport: { w: number; h: number };
/** The host element under the click. null only if none could be resolved.
* For /