@siteping/widget 0.12.1 → 0.12.3

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.
Files changed (53) hide show
  1. package/README.md +10 -10
  2. package/dist/chunk-7LL25BRN.cjs +9 -0
  3. package/dist/chunk-7LL25BRN.cjs.map +1 -0
  4. package/dist/chunk-HFZPYBZG.js +9 -0
  5. package/dist/chunk-HFZPYBZG.js.map +1 -0
  6. package/dist/de-3ANNNYIA.js +2 -0
  7. package/dist/{de-U3V6TPCZ.js.map → de-3ANNNYIA.js.map} +1 -1
  8. package/dist/{de-GCYQ3JCH.cjs → de-4N2W5ASV.cjs} +2 -2
  9. package/dist/{de-GCYQ3JCH.cjs.map → de-4N2W5ASV.cjs.map} +1 -1
  10. package/dist/es-6M2N2ZUW.cjs +2 -0
  11. package/dist/{es-435K3LIV.cjs.map → es-6M2N2ZUW.cjs.map} +1 -1
  12. package/dist/es-GPIJU4FY.js +2 -0
  13. package/dist/{es-CSZUS23Z.js.map → es-GPIJU4FY.js.map} +1 -1
  14. package/dist/fr-6QAB4LJP.js +2 -0
  15. package/dist/{fr-DVOFE3WF.js.map → fr-6QAB4LJP.js.map} +1 -1
  16. package/dist/fr-KAHW2UVM.cjs +2 -0
  17. package/dist/{fr-RKRDGOTP.cjs.map → fr-KAHW2UVM.cjs.map} +1 -1
  18. package/dist/index.cjs +3 -3
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.global.js +2 -2
  21. package/dist/index.global.js.map +1 -1
  22. package/dist/index.js +3 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/it-4TJ7GPQP.cjs +2 -0
  25. package/dist/{it-COLRVG26.cjs.map → it-4TJ7GPQP.cjs.map} +1 -1
  26. package/dist/it-S7QUW7XF.js +2 -0
  27. package/dist/{it-ZWPEN22A.js.map → it-S7QUW7XF.js.map} +1 -1
  28. package/dist/panel-E5DL4OG4.cjs +2 -0
  29. package/dist/panel-E5DL4OG4.cjs.map +1 -0
  30. package/dist/panel-TDZWSRVV.js +2 -0
  31. package/dist/panel-TDZWSRVV.js.map +1 -0
  32. package/dist/pt-2P3TRISG.js +2 -0
  33. package/dist/{pt-WGEJRQFC.js.map → pt-2P3TRISG.js.map} +1 -1
  34. package/dist/pt-RAL7HZ3Q.cjs +2 -0
  35. package/dist/{pt-QQX5CA7D.cjs.map → pt-RAL7HZ3Q.cjs.map} +1 -1
  36. package/package.json +1 -1
  37. package/dist/chunk-ISSCC4XP.js +0 -9
  38. package/dist/chunk-ISSCC4XP.js.map +0 -1
  39. package/dist/chunk-QDJDQQ6E.cjs +0 -9
  40. package/dist/chunk-QDJDQQ6E.cjs.map +0 -1
  41. package/dist/de-U3V6TPCZ.js +0 -2
  42. package/dist/es-435K3LIV.cjs +0 -2
  43. package/dist/es-CSZUS23Z.js +0 -2
  44. package/dist/fr-DVOFE3WF.js +0 -2
  45. package/dist/fr-RKRDGOTP.cjs +0 -2
  46. package/dist/it-COLRVG26.cjs +0 -2
  47. package/dist/it-ZWPEN22A.js +0 -2
  48. package/dist/panel-4CIN3Y66.js +0 -2
  49. package/dist/panel-4CIN3Y66.js.map +0 -1
  50. package/dist/panel-TD4UUEZA.cjs +0 -2
  51. package/dist/panel-TD4UUEZA.cjs.map +0 -1
  52. package/dist/pt-QQX5CA7D.cjs +0 -2
  53. package/dist/pt-WGEJRQFC.js +0 -2
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../core/src/type-utils.ts","../../core/src/errors.ts","../../core/src/types.ts","../../core/src/deep-link.ts","../../core/src/email.ts","../../core/src/i18n.ts","../../core/src/wire.ts","../src/constants.ts","../src/dom-utils.ts","../src/host-isolation.ts","../src/icons.ts","../src/styles/theme.ts","../src/viewport.ts","../src/i18n/en.ts","../src/i18n/index.ts","../src/export-utils.ts","../src/panel-bulk.ts","../src/panel-actions.ts","../src/panel-detail.ts","../src/panel-sort.ts","../src/panel-stats.ts","../src/shortcuts.ts"],"names":["isRecord","value","hasOwn","key","SitepingError","message","code","retryable","SitepingNetworkError","SitepingValidationError","status","SitepingAuthError","SITEPING_ERROR_CODES","isSitepingError","error","IDENTITY_FIELD_MAX_LENGTH","FEEDBACK_STATUSES","CLOSED_FEEDBACK_STATUSES","isClosedStatus","toFeedbackUpdate","closedAt","COMMENT_BODY_MAX_LENGTH","StoreDuplicateError","options","hasErrorCode","isStoreDuplicate","flattenAnnotation","ann","ANCHOR_ELEMENT_TAG_MAX","ANCHOR_ELEMENT_ID_MAX","isSafeImageUrl","url","isLoopbackHttp","protocol","hostname","EMAIL_PATTERN","isValidEmail","normaliseLang","locale","canonicalTag","canonicalizeLocale","canonical","createI18n","en","loaders","builtins","overrides","isBuiltinNonEn","lang","translations","interpolate","template","params","match","name","tWithParams","newClientId","feedbackQueryToSearchParams","query","mergeRequestHeaders","defaults","extra","merged","withSearchParams","endpoint","hashAt","base","separator","errorFromResponse","response","label","text","fieldOf","isThreadFull","networkErrorFromException","detail","COMPACT_MEDIA","parseSvg","svgString","svg","attr","el","tag","attrs","element","setText","onClickOutside","container","onOutside","root","host","inTree","e","onPage","setHidden","hidden","setInert","inert","hasFocusWithin","active","setButtonLoading","btn","snapshot","isMacPlatform","uaData","formatRelativeDate","isoString","diff","seconds","rtf","minutes","hours","days","OUTSIDE_INTERACTION_EVENTS","HOST_HIDING_ATTRIBUTES","surfaces","ownInert","shadowRoots","escapeLayers","tabListeners","composedParent","node","isWidgetSurface","current","isSurfaceTarget","target","stopAtSurface","event","keepTypingInWidget","field","focusedTarget","isolateFromHost","surface","shadowRoot","type","unhide","setSurfaceInert","registerEscapeLayer","scope","isOpen","layers","addSurfaceKeydownListener","listener","listeners","isHostModalOpen","dialog","focused","willWidgetConsumeEscape","installHostIsolationGuard","hideFromHost","ICON_SITEPING","ICON_EYE","ICON_EYE_OFF","ICON_CLOSE","ICON_SEARCH","ICON_CHECK","ICON_ARCHIVE","ICON_QUESTION","ICON_CHANGE","ICON_BUG","ICON_OTHER","ICON_LAYERS","ICON_DOT_OPEN","ICON_CHEVRON_DOWN","ICON_UNDO","ICON_TRASH","ICON_USER","ICON_LIST","ICON_EDIT","DEFAULT_ACCENT","HEX6_RE","HEX3_RE","HEX8_RE","normalizeHex","raw","short","mixHex","hex","toward","amount","out","i","from","channel","luminance","sum","weight","c","readableOn","bg","background","color","step","DARK_QUERY","prefersDark","watchColorScheme","onChange","resolveTheme","theme","buildThemeColors","accent","dark","getTypeColor","colors","getStatusColor","getStatusBgColor","getTypeBgColor","cssVariables","matches","isCompactViewport","isCoarsePointer","prefersReducedMotion","trackKeyboardInset","vv","update","i18n","m","registerLocale","loadLocale","createT","getTypeLabel","t","getStatusLabel","ICON_EXPORT","ICON_CSV","ICON_JSON","EXPORT_CSS","CSV_COLUMNS","escapeCsvField","guarded","feedbacksToCsv","feedbacks","header","rows","fb","col","feedbacksToJson","downloadFile","content","filename","mimeType","blob","anchor","ExportButton","_colors","getFeedbacks","csvOption","jsonOption","iconSvg","labelText","onClick","option","iconWrap","labelEl","format","projectName","date","safeName","ICON_CHECKBOX","ICON_CHECKBOX_CHECKED","BULK_CSS","BulkActions","callbacks","actions","deselectBtn","feedbackId","wrapper","feedbackIds","checkbox","id","toggleAll","prevSelected","count","visible","ids","resolve","del","resolveLabel","deleteLabel","isChecked","allSelected","escapedId","card","ICON_TAGS","ICON_ATTRS","INERT_CALLS","isInertValue","sanitizeIconNode","child","parseActionIcon","markup","safeHref","href","snapshotFeedback","feedback","_key","isButton","a","isLink","normalizePanelActions","items","action","index","where","icon","ICON_ARROW_LEFT","ICON_MAP_PIN","ICON_LINK","ICON_CALENDAR","ICON_MONITOR","ICON_CODE","ICON_CROSSHAIR","ICON_CHEVRON","ICON_TERMINAL","DETAIL_CSS","parseBrowser","ua","formatFullDate","extractPathname","truncate","str","max","hasDiagnostics","diagnostics","consoleLen","networkLen","formatDuration","ms","customContent","span","DetailView","customActions","backBtn","number","title","badge","sectionIndex","statusSection","op","messageSection","messageSectionTitle","messageBlock","thread","screenshotSection","screenshotSectionTitle","img","metaSection","metaSectionTitle","annSection","annSectionTitle","annTitleText","diagSection","diagSectionTitle","diagTitleText","refocus","section","isClosed","sectionTitle","statusModifier","dotColors","statusRow","pill","dot","pillLabel","row","item","control","link","meta","pathname","email","resolvedDate","dateLabel","buildValue","info","tagDisplay","pct","fraction","gotoBtn","gotoLabel","lost","diag","consoleEntries","networkEntries","errorCount","toggle","toggleLabel","leftRow","counts","consoleCount","networkCount","body","group","list","entry","level","msg","method","next","task","b","disabled","ICON_SORT","ICON_PAGE","TYPE_ORDER","sortFeedbacks","mode","sorted","typeA","typeB","statusA","statusB","truncatePath","path","maxLength","ellipsis","keep","groupFeedbacksByPage","groups","existing","createPageGroupHeader","pagePath","chevronWrap","pageIcon","pathEl","displayPath","countEl","isExpanded","PanelSortControls","sortIcon","groupIcon","groupLabel","opt","SORT_CSS","STATS_CSS","PanelStats","itemOpen","dotOpen","labelOpen","itemClosed","dotClosed","labelClosed","itemBugs","dotBugs","labelBugs","progress","track","total","openCount","closedCount","bugCount","progressText","ICON_KEYBOARD","getFocusedCardIndex","listContainer","cards","activeCard","activeIdx","focusCardByIndex","clamped","SHORTCUT_DEFS","SHORTCUTS_CSS","ICON_CLOSE_SM","KeyboardShortcuts","n","handler","overlay","titleText","closeBtn","grid","def","keysWrap","sep","kbd","desc"],"mappings":"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","file":"chunk-ISSCC4XP.js","sourcesContent":["/**\n * General-purpose TypeScript utility types used across `@siteping/*`.\n *\n * These are kept dependency-free and re-exported from the package entry\n * so adapters and integrators can rely on the same primitives the core\n * uses internally.\n */\n\n/**\n * Force TypeScript to expand a computed type into a flat object literal in\n * tooltips and error messages. Purely cosmetic — same structural type, just\n * easier to read.\n *\n * @example\n * type Raw = Omit<FeedbackRecord, \"annotations\"> & { annotations: number };\n * type Pretty = Prettify<Raw>; // displayed as a flat object\n */\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\n/**\n * Returns `Y` when `A` is exactly assignable to `B` and vice-versa,\n * otherwise `N`. Powers compile-time equality assertions.\n */\nexport type IfEquals<A, B, Y = true, N = false> =\n (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? Y : N;\n\n/**\n * Compile-time exact-type guard — resolves to `true` when `Actual` and\n * `Expected` are identical, `never` otherwise. Assign the result to a\n * `const _lock: AssertEqual<A, B> = true;` so any drift becomes a compile\n * error at the declaration site.\n */\nexport type AssertEqual<Actual, Expected> = IfEquals<Actual, Expected, true, never>;\n\n/**\n * JSON-serialized shape of `T` — the wire form produced by `Response.json()`\n * / `JSON.stringify`: `Date` becomes ISO `string` (nullability preserved),\n * arrays are serialized element-wise, everything else is untouched.\n *\n * Used to derive the `*Response` API types from the `*Record` store types so\n * the two can never drift: add a field to `FeedbackRecord` and\n * `FeedbackResponse` follows automatically.\n */\nexport type Serialized<T> = {\n [K in keyof T]: T[K] extends Date\n ? string\n : T[K] extends Date | null\n ? string | null\n : T[K] extends (infer U)[]\n ? Serialized<U>[]\n : T[K];\n};\n\n/**\n * Read-only all the way down, for plain data that is deeply frozen at\n * runtime: nested objects get read-only properties, arrays become\n * `readonly` arrays.\n */\nexport type DeepReadonly<T> = T extends readonly (infer U)[]\n ? readonly DeepReadonly<U>[]\n : T extends object\n ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T;\n\n/**\n * Type guard that narrows `value` to a non-null `Record<PropertyKey, unknown>`.\n * Useful when validating arbitrary inputs before reading fields.\n */\nexport function isRecord(value: unknown): value is Record<PropertyKey, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Returns true when `value` is an object that exposes the requested key.\n * Type-narrows `value` so the property can be accessed without further\n * casting — a strictly typed replacement for `\"k\" in obj`.\n *\n * Named after the standardised `Object.hasOwn` helper rather than the\n * legacy `Object.prototype.hasOwnProperty`, which the linter forbids\n * shadowing.\n */\nexport function hasOwn<K extends PropertyKey>(value: unknown, key: K): value is Record<K, unknown> {\n return isRecord(value) && key in value;\n}\n","import { hasOwn } from \"./type-utils.js\";\n\n/**\n * Typed error hierarchy for Siteping client/server boundaries.\n *\n * Consumers match an error with a guard — `isSitepingError`,\n * `isSitepingConfigError`, a store guard such as `isStoreNotFound` — then\n * read `code` / `retryable` instead of pattern-matching messages. The guards\n * are structural, never `instanceof`: every published package bundles its\n * own copy of these classes (tsup `noExternal`), so an error one package\n * throws fails `instanceof` against another's class. The widget and the\n * dashboard export the guard and the types, not the classes.\n *\n * Usage on the widget side (api-client.ts):\n * - fetch failures / aborts / timeouts → `SitepingNetworkError` (retryable)\n * - HTTP 4xx (except 401/403/408/429) → `SitepingValidationError` (not retryable)\n * - HTTP 401 / 403 → `SitepingAuthError` (not retryable)\n * - HTTP 5xx / 408 / 429 → `SitepingError` code `\"SERVER\"` (retryable)\n *\n * Store mode (store-client.ts): a `createFeedback` that does not settle in\n * time → `SitepingError` with code `\"TIMEOUT\"` (retryable); a reply to a\n * store that keeps no comments → `\"SERVER\"`, not retryable.\n *\n * `retryable` says whether the same request may succeed later, unchanged —\n * meta information for host apps that wire their own retry/queue/backoff\n * strategy; the widget already retries and queues transient failures itself.\n */\n\n/** Discriminant string carried by every `SitepingError`. */\nexport type SitepingErrorCode = \"NETWORK\" | \"VALIDATION\" | \"AUTH\" | \"SERVER\" | \"TIMEOUT\";\n\nexport class SitepingError<TCode extends SitepingErrorCode = SitepingErrorCode> extends Error {\n readonly code: TCode;\n readonly retryable: boolean;\n\n constructor(message: string, code: TCode, retryable: boolean) {\n super(message);\n this.code = code;\n this.retryable = retryable;\n this.name = \"SitepingError\";\n }\n}\n\n/** Network-level failure: connection refused, DNS, CORS, timeout, abort. Retryable. */\nexport class SitepingNetworkError extends SitepingError<\"NETWORK\"> {\n constructor(message: string) {\n super(message, \"NETWORK\", true);\n this.name = \"SitepingNetworkError\";\n }\n}\n\n/**\n * Server rejected the request (4xx, not auth). Validation problem on the\n * client side. `status` is the HTTP status when a response said so: a 404\n * (the target is gone) and a 409 (a full thread) call for other answers\n * than a 400.\n */\nexport class SitepingValidationError extends SitepingError<\"VALIDATION\"> {\n readonly status: number | undefined;\n\n constructor(message: string, status?: number) {\n super(message, \"VALIDATION\", false);\n this.name = \"SitepingValidationError\";\n this.status = status;\n }\n}\n\n/** Server rejected auth (401 or 403). Not retryable without fresh credentials. */\nexport class SitepingAuthError extends SitepingError<\"AUTH\"> {\n /**\n * `401`: the credentials are missing or no longer work — drop a dead token\n * on this one. `403`: they work, and the server's policy refuses this request.\n */\n readonly status: 401 | 403;\n\n constructor(message: string, status: 401 | 403) {\n super(message, \"AUTH\", false);\n this.name = \"SitepingAuthError\";\n this.status = status;\n }\n}\n\n/**\n * Every error the widget and the dashboard report for a failed call, told\n * apart by `code`: narrowing on it reaches a subclass's own fields, such as\n * an `AUTH` error's `status`.\n */\nexport type SitepingClientError =\n | SitepingNetworkError\n | SitepingValidationError\n | SitepingAuthError\n | SitepingError<\"SERVER\" | \"TIMEOUT\">;\n\nconst SITEPING_ERROR_CODES: readonly string[] = [\n \"NETWORK\",\n \"VALIDATION\",\n \"AUTH\",\n \"SERVER\",\n \"TIMEOUT\",\n] satisfies readonly SitepingErrorCode[];\n\n/**\n * Whether `error` is a `SitepingError` (any subclass) — matched on its\n * fields, a known `code` and a boolean `retryable`, so it holds for an error\n * another package's copy of the class threw. Anything else reaching an\n * `onError` (a store's own error, a panel action's) fails it.\n */\nexport function isSitepingError(error: unknown): error is SitepingClientError {\n return (\n hasOwn(error, \"code\") &&\n hasOwn(error, \"retryable\") &&\n typeof error.retryable === \"boolean\" &&\n typeof error.code === \"string\" &&\n SITEPING_ERROR_CODES.includes(error.code)\n );\n}\n\n/** Stable codes of the errors a handler factory throws for options it cannot run with. */\nexport type SitepingConfigErrorCode =\n | \"CONFIG_STORE_MISSING\"\n | \"CONFIG_MAX_BODY_BYTES_INVALID\"\n | \"CONFIG_OWNERSHIP_CHECK_MISSING\"\n | \"CONFIG_WEBHOOK_TIMEOUT_INVALID\"\n | \"CONFIG_API_KEY_REQUIRED\";\n\n/**\n * Thrown by `createSitepingHandler` while it is created, never per request,\n * when its options cannot run safely. The message says how to fix it; branch\n * on `code`, after {@link isSitepingConfigError}.\n */\nexport class SitepingConfigError extends Error {\n readonly code: SitepingConfigErrorCode;\n\n constructor(code: SitepingConfigErrorCode, message: string) {\n super(message);\n this.code = code;\n this.name = \"SitepingConfigError\";\n }\n}\n\n/**\n * Whether `error` is a `SitepingConfigError` — matched on its `name` and a\n * `CONFIG_` code rather than `instanceof`, for the same cross-bundle reason\n * as {@link isSitepingError}.\n */\nexport function isSitepingConfigError(error: unknown): error is SitepingConfigError {\n return (\n hasOwn(error, \"name\") &&\n error.name === \"SitepingConfigError\" &&\n hasOwn(error, \"code\") &&\n typeof error.code === \"string\" &&\n error.code.startsWith(\"CONFIG_\")\n );\n}\n","import { type AssertEqual, type DeepReadonly, hasOwn, type Prettify, type Serialized } from \"./type-utils.js\";\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\n/** FAB anchor — bottom-corner placement supported by the widget. */\nexport type SitepingPosition = \"bottom-right\" | \"bottom-left\";\n\n/**\n * Visual theme of the widget and the dashboard — `auto` follows the system\n * preference, live: an OS switch mid-session re-themes the UI.\n */\nexport type SitepingTheme = \"light\" | \"dark\" | \"auto\";\n\n/** Built-in UI locales shipped with the widget. */\nexport const BUILTIN_LOCALES = [\"en\", \"fr\", \"de\", \"es\", \"it\", \"pt\", \"ru\", \"ja\", \"ko\"] as const;\nexport type BuiltinLocale = (typeof BUILTIN_LOCALES)[number];\n\n/**\n * Locale identifier accepted by the widget. Built-in locales are kept as\n * literal strings so editors auto-complete them, but arbitrary BCP-47 tags\n * are also accepted (custom dictionaries registered via `registerLocale`).\n */\nexport type SitepingLocale = BuiltinLocale | (string & {});\n\n/**\n * Reasons reported through `SitepingConfig.onSkip` — production environment,\n * viewport narrower than `minViewportWidth` (`\"mobile\"`), or server-side\n * rendering (no `window`/`document`).\n */\nexport type SitepingSkipReason = \"production\" | \"mobile\" | \"ssr\";\n\n/** Per-channel + per-buffer-size diagnostics configuration. */\nexport interface DiagnosticsCaptureOptions {\n console?: boolean | undefined;\n network?: boolean | undefined;\n /** Console buffer size — default and maximum 50 (the server's cap; larger values are clamped). */\n maxConsoleEntries?: number | undefined;\n /** Failed-request buffer size — default and maximum 20 (the server's cap; larger values are clamped). */\n maxNetworkEntries?: number | undefined;\n}\n\n/** Identity payload supplied by the host application — bypasses the modal. */\nexport interface SitepingIdentity {\n name: string;\n email: string;\n}\n\n/**\n * Max length of an identity's `name` and `email` — the HTTP schema's\n * `authorName` / `authorEmail` cap. The widget's modal enforces it so a value\n * it persists is never a 400 on every later submission.\n */\nexport const IDENTITY_FIELD_MAX_LENGTH = 200;\n\n/**\n * Max length of a `projectName` and of a feedback's or comment's `clientId`\n * — the identifiers the stores index. 191 characters is the longest\n * `VARCHAR` every MySQL version indexes under `utf8mb4` (767 bytes), which\n * is why Prisma maps a plain `String` to `VARCHAR(191)` there: the HTTP\n * schema caps them at that length, so their columns need no prefix index.\n */\nexport const PROJECT_NAME_MAX_LENGTH = 191;\nexport const CLIENT_ID_MAX_LENGTH = 191;\n\n/** Deep-link configuration — controls how a feedback id is read from the URL. */\nexport interface SitepingDeepLinkOptions {\n /** Query parameter name carrying the feedback id. Defaults to `\"siteping\"`. */\n param?: string | undefined;\n}\n\n/**\n * The feedback handed to panel action callbacks: a detached, deeply frozen\n * copy, typed read-only all the way down. Type your own helpers with it — a\n * `FeedbackResponse` parameter does not accept the frozen copy.\n */\nexport type SitepingPanelActionFeedback = DeepReadonly<FeedbackResponse>;\n\n/**\n * Helpers passed to a panel action's `onAction` as its second argument.\n * Both do nothing once the widget has been destroyed.\n */\nexport interface SitepingPanelActionContext {\n /**\n * Re-fetch the panel list and markers, then re-render the detail view with\n * the updated feedback — or go back to the list when it no longer matches\n * the panel filters. Call it after your action changed the feedback\n * server-side (e.g. moved it to `in_progress` once a ticket exists).\n */\n refresh: () => Promise<void>;\n /** Close the feedback panel. */\n close: () => void;\n}\n\n/**\n * Fields shared by both kinds of {@link SitepingPanelAction}.\n *\n * Do not use this type directly — use {@link SitepingPanelAction}.\n */\nexport interface SitepingPanelActionBase {\n /** Stable, unique identifier — becomes `data-action-id` on the rendered control. */\n id: string;\n /**\n * Visible label, rendered as plain text. Host-provided verbatim — not\n * routed through the widget i18n, since hosts localize their own strings.\n */\n label: string;\n /**\n * Optional inline SVG markup rendered before the label. It is parsed\n * inertly and reduced to plain shapes — scripts, event handlers, links,\n * styles and external references are dropped — but keep it static markup\n * you control. Markup that is not an `<svg>` is ignored with a warning.\n */\n icon?: string | undefined;\n /**\n * Per-feedback visibility predicate. Return `false` to omit the action\n * for that feedback. Defaults to always visible. A throw hides the action\n * and is reported through `onError`.\n */\n visible?: ((feedback: SitepingPanelActionFeedback) => boolean) | undefined;\n}\n\n/** A panel action rendered as a button that runs host code. */\nexport interface SitepingPanelButtonAction extends SitepingPanelActionBase {\n /**\n * Invoked on click. While a returned promise is pending the detail view's\n * action buttons are disabled and the clicked button shows a spinner —\n * also after `context.refresh()` re-renders the view, and when the user\n * comes back to that feedback.\n * Throws and rejections are reported through `onError` and restore the\n * buttons; the detail view stays open either way.\n */\n onAction: (feedback: SitepingPanelActionFeedback, context: SitepingPanelActionContext) => void | Promise<void>;\n /** Not available on a button action — use either `onAction` or `href`, never both. */\n href?: never;\n}\n\n/** A panel action rendered as a link. */\nexport interface SitepingPanelLinkAction extends SitepingPanelActionBase {\n /**\n * Link target — a URL, or a function building one from the feedback.\n * Relative URLs resolve against the page. Only `http:`, `https:` and\n * `mailto:` are rendered: a static `href` with any other scheme\n * (`javascript:` included) skips the action with a warning; a function\n * returning one hides the action and is reported through `onError`. Web\n * links open in a new tab with `rel=\"noopener noreferrer\"`.\n */\n href: string | ((feedback: SitepingPanelActionFeedback) => string);\n /** Not available on a link action — use either `onAction` or `href`, never both. */\n onAction?: never;\n}\n\n/**\n * A host-defined action rendered in the feedback detail view, below the\n * built-in Resolve / Delete buttons: a button running your code\n * (`onAction`) or a link (`href`) — never both.\n *\n * Hosts use this to bridge feedbacks into their own systems — create a\n * ticket, dispatch to a bot, open the feedback in their tracker — without\n * forking the panel. Callbacks receive a detached, deeply frozen copy of\n * the feedback: read it freely, it can never alter what the panel displays.\n */\nexport type SitepingPanelAction = SitepingPanelButtonAction | SitepingPanelLinkAction;\n\n/**\n * Extra request headers for HTTP mode — a static map, or a factory (sync or\n * async) invoked once per request to produce fresh values (e.g. a short-lived\n * session token).\n */\nexport type SitepingHeadersOption =\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n\n/**\n * Options shared by both widget modes (HTTP and direct store).\n *\n * Do not use this type directly — use {@link SitepingConfig}, the\n * discriminated union that adds the mode-specific fields.\n */\nexport interface SitepingBaseConfig {\n /** Required — project identifier used to scope feedbacks */\n projectName: string;\n /** FAB position — defaults to 'bottom-right' */\n position?: SitepingPosition | undefined;\n /**\n * Show the \"toggle markers visibility\" item in the FAB radial menu.\n * Defaults to `true` (current behavior). Set to `false` to hide that\n * item entirely — useful for hosts that always want markers visible\n * (e.g. dedicated review tools) or that find the eye icon redundant\n * when no marker is on screen.\n *\n * Hiding the item also removes its keyboard navigation slot — the\n * remaining two items still respond to ArrowUp/ArrowDown/Home/End.\n * The marker-visibility state itself is unaffected; markers stay\n * visible (the previous default state) and `annotations:toggle` is\n * simply never emitted from the FAB.\n */\n showAnnotationsToggle?: boolean | undefined;\n /** Accent color for the widget UI — defaults to '#0066ff' */\n accentColor?: string | undefined;\n /**\n * Render the widget even when it would normally be skipped — this bypasses\n * BOTH the production-environment guard AND the `minViewportWidth` guard.\n * It does NOT bypass the SSR guard: without `window`/`document` the widget\n * never renders and `onSkip(\"ssr\")` fires instead.\n * Defaults to false. Use it for dedicated review tools, staging environments,\n * or responsive testing where you always want the widget present.\n */\n forceShow?: boolean | undefined;\n /**\n * Minimum viewport width (px) at or above which the widget renders. Below it,\n * the widget is skipped and `onSkip(\"mobile\")` fires. Defaults to `0`: the\n * widget renders at every width, and phones get a compact layout (bottom\n * sheets, touch-sized controls).\n *\n * Set it (e.g. `768`) to keep the widget off small screens, or use\n * `forceShow` to bypass the viewport check entirely.\n */\n minViewportWidth?: number | undefined;\n /** Enable debug logging of lifecycle events — defaults to false */\n debug?: boolean | undefined;\n /** Color theme — defaults to `light`; `auto` follows the system preference live. */\n theme?: SitepingTheme | undefined;\n /** UI locale — defaults to 'en'. Built-in: en, fr, de, es, it, pt (Brazilian), ru, ja, ko. Any other string falls back to English. */\n locale?: SitepingLocale | undefined;\n /**\n * Returns the current page scope for annotations and panel filtering.\n * Called on initial markers load and on `instance.refresh()`.\n *\n * Default: `{ url: window.location.pathname, urlPattern: null }` — annotations\n * are scoped strictly to the current pathname.\n *\n * Apps with parameterized routes (e.g. React Router) should return both the\n * concrete URL and the route template (e.g. `/orders/:orderId`) so the panel\n * can offer a \"this type of page\" filter that groups feedbacks by template.\n */\n getPageScope?: (() => PageScope) | undefined;\n /**\n * When true (default), the widget filters initial markers and panel results\n * by `feedback.url === scope.url`, so annotations created on one page never\n * leak to other pages — even if their CSS selector accidentally matches.\n * Set to `false` for a project-wide view: the panel lists every feedback of\n * the project, and a marker shows wherever its anchor resolves — for\n * feedback on what every page shares, such as a header or a footer.\n */\n scopeAnnotationsByUrl?: boolean | undefined;\n /**\n * Capture a JPEG screenshot of the annotated area on submit. Defaults to\n * `false` — opt-in because:\n *\n * - it adds runtime weight (~60 KB gzip dynamic chunk for html2canvas-pro,\n * loaded only on first capture),\n * - it embeds page content in the feedback (privacy/GDPR consideration —\n * inform end users in your widget host UI when enabling).\n *\n * `html2canvas-pro` ships as a regular dependency of `@siteping/widget` so the\n * dynamic import always resolves; you don't need to install anything extra.\n *\n * **Masking sensitive elements:** add `data-siteping-ignore=\"true\"` to any\n * element you do NOT want captured (password fields, credit-card forms,\n * API tokens shown in the UI, etc.). The capture predicate skips matching\n * elements *and their descendants*. Do this BEFORE turning on screenshots\n * in production — once a feedback is saved, the screenshot is in your DB\n * (or object storage) regardless of what was on the page.\n */\n enableScreenshot?: boolean | undefined;\n /**\n * Enable right-click (`contextmenu`) to instantly open the comment composer\n * at the cursor location. When enabled, a document-level listener intercepts\n * right-clicks, prevents the browser's native context menu, and enters the\n * annotation flow anchored to the element under the cursor. Defaults to\n * `false` — the browser's native context menu is never hijacked unless the\n * host explicitly opts in.\n *\n * Keyboard-triggered context menus (≣ Menu key, Shift+F10) always get the\n * native menu; only mouse right-click and touch/pen long-press open the\n * composer — a long-press only where the browser fires `contextmenu` for\n * it, which iOS and iPadOS never do (see the note below).\n *\n * **Modifier-key escape hatch:** holding Shift, Ctrl, Alt, or Meta while\n * right-clicking always falls through to the native context menu, giving\n * users (and devtools) an escape hatch regardless of this setting.\n *\n * Right-clicks on SitePing's own UI (FAB, panel, markers, popup) are\n * ignored — the native menu is shown as expected.\n *\n * Note: on Android, `contextmenu` fires on long-press — touch users open the\n * composer by long-pressing, on phones too since the widget renders at every\n * width by default (see `minViewportWidth`). On iPhone and iPad a long-press\n * never fires `contextmenu` (WebKit bug 213953): users there open annotate\n * mode from the floating button and tap the element.\n */\n enableRightClickComment?: boolean | undefined;\n /**\n * Capture the last few `console.*` calls and failed network requests\n * (HTTP >= 400 or network error) at the moment a feedback is submitted.\n *\n * Lets reviewers replay the technical context that led to the report —\n * stack traces, 500 responses, dead third-party scripts. Great for the\n * \"the page just doesn't work\" feedback that contains zero detail.\n *\n * - `true` — capture with defaults (50 console / 20 network entries).\n * - `false` (default) — no capture, no monkey-patching.\n * - object — per-channel toggles + smaller buffer sizes (values above the\n * 50 / 20 server caps are clamped so submissions never fail validation).\n *\n * **Privacy considerations:** console messages may contain anything the\n * host page logs, including user data. Failed network requests record the\n * URL without its credentials, query string or hash, and never the\n * response body.\n * Inform end users before enabling in environments where they might log\n * sensitive values.\n */\n captureDiagnostics?: boolean | DiagnosticsCaptureOptions | undefined;\n /** Called when the widget is skipped (production mode, viewport under `minViewportWidth`, SSR — no DOM) */\n onSkip?: ((reason: SitepingSkipReason) => void) | undefined;\n /**\n * Auto-focus a specific annotation when its ID appears in the URL query\n * string. Lets hosts deeplink directly into a feedback from external\n * systems (Zammad tickets, Slack notifications, dashboard rows).\n *\n * When enabled, the widget reads the configured query parameter from\n * `window.location.search` right after the initial markers load. If the\n * value matches a visible feedback ID, the widget scrolls the annotation\n * into view, pins its highlight, and pulses the marker — the same visual\n * affordance a marker click produces — then opens the panel on that\n * feedback's detail view, where its discussion thread is.\n *\n * - `false` / `undefined` (default): no URL parsing. Existing behavior\n * unchanged, no host URL inspection.\n * - `true`: enabled with default query parameter name `siteping`.\n * - object: enabled with a custom parameter name. Use this to avoid\n * clashes with host-app query keys.\n *\n * Only the initial load triggers focus. Subsequent URL changes (SPA\n * navigation, `history.pushState`, hash updates) are ignored —\n * deliberate, to avoid surprising re-scrolls during normal browsing.\n * Hosts that need re-focus on route change can call\n * `instance.focusFeedback(id)` explicitly.\n */\n deepLink?: boolean | SitepingDeepLinkOptions | undefined;\n /**\n * Automatically re-fetch feedbacks when the page changes during client-side\n * (SPA) navigation. Enabled by default.\n *\n * The widget is normally mounted once (singleton) inside a persistent layout\n * — e.g. a Next.js App Router `layout.tsx`, which does NOT remount on\n * client-side navigation. Without this, init runs a single time and both the\n * panel list and the page markers stay frozen on the page where the widget\n * first mounted. With it on, the widget patches the History API\n * (`pushState`/`replaceState`, which SPA routers call instead of triggering\n * `popstate`) and listens for `popstate`/`hashchange`, then re-fetches when\n * the scope key (`getPageScope().url` + template) actually changes.\n *\n * This re-fetches data only — it deliberately does NOT re-focus or re-scroll\n * to an annotation (deep-link focus stays initial-load only; see `deepLink`),\n * so normal browsing is never interrupted by a surprise scroll.\n *\n * - `true` (default) — watch navigation and re-fetch on route change.\n * - `false` — never touch the History API; hosts drive updates manually via\n * `instance.refresh()`.\n */\n watchNavigation?: boolean | undefined;\n /**\n * Pre-fill author identity from the host application — typically the\n * currently signed-in user. When set, the widget uses these values\n * directly and never shows the identity modal, even on first feedback.\n * It names the reviewer who sends feedback — the dashboard's `author` is\n * the team member who replies, a different person.\n *\n * Use case: SSO-integrated apps where the end user is already\n * authenticated by the host. Avoids the awkward \"enter your name and\n * email\" prompt for users the host already knows.\n *\n * When unset (default), the widget falls back to localStorage and shows\n * the modal on first feedback as before — existing behavior unchanged.\n *\n * Note: `config.identity` is **not** persisted to localStorage. It is\n * read at widget init time, not on every render. Hosts that need live\n * identity updates after sign-in/sign-out should currently remount the\n * widget (e.g. via a React `key` on the wrapping component). See\n * https://github.com/NeosiaNexus/SitePing/issues/85 for tracking a\n * future enhancement that propagates identity updates without a remount.\n */\n identity?: SitepingIdentity | undefined;\n /**\n * Host-defined actions rendered in the feedback detail view, below the\n * built-in Resolve and Delete buttons. Read once when the panel loads:\n * entries without a non-empty `id` and `label`, without exactly one of\n * `onAction` / `href`, with an unsafe static `href`, or reusing an earlier\n * `id` are skipped with a console warning. See {@link SitepingPanelAction}.\n */\n panelActions?: readonly SitepingPanelAction[] | undefined;\n /**\n * Reviewer mode: hide the actions that triage feedback — resolve, reopen,\n * delete, the bulk actions, \"Delete all\" — and keep creating, browsing\n * and replying. Defaults to `false`. The server's `permissions` hide\n * what it would refuse on top of it. It only hides: the server decides\n * what it accepts. Read once when the panel loads.\n */\n readOnly?: boolean | undefined;\n\n // Events\n /** Called when the feedback panel is opened. */\n onOpen?: (() => void) | undefined;\n /** Called when the feedback panel is closed. */\n onClose?: (() => void) | undefined;\n /** Called after a feedback is successfully submitted. */\n onFeedbackSent?: ((feedback: FeedbackResponse) => void) | undefined;\n /** Called after a reply is posted from the panel's discussion thread. */\n onCommentAdded?: ((comment: CommentResponse) => void) | undefined;\n /**\n * Called when a feedback API call fails.\n *\n * The widget always emits a `SitepingError` (or a subclass:\n * `SitepingNetworkError`, `SitepingValidationError`, `SitepingAuthError`)\n * for a failed call — match it with `isSitepingError(error)` (not\n * `instanceof`: the classes aren't exported), then read `error.code`\n * (`\"NETWORK\" | \"VALIDATION\" | \"AUTH\" | \"SERVER\" | \"TIMEOUT\"`),\n * `error.retryable` and an `AUTH` error's `status` (`401` for missing or\n * dead credentials, `403` for a refusal). The type is widened to `Error`\n * because a store in `store` mode may throw its own errors, which reach\n * here unchanged.\n *\n * Also receives whatever a `panelActions` callback throws or rejects with\n * (non-`Error` values are wrapped). Those host failures are not API\n * failures, so they are not emitted on the public `feedback:error` event.\n * The widget has no UI for them, so they are also always logged with\n * `console.error`, whether or not `onError` is set.\n */\n onError?: ((error: Error) => void) | undefined;\n /** Called when the user starts drawing an annotation. */\n onAnnotationStart?: (() => void) | undefined;\n /** Called when the user finishes drawing an annotation. */\n onAnnotationEnd?: (() => void) | undefined;\n}\n\n/**\n * HTTP mode — the widget talks to a server endpoint backed by a store\n * adapter (e.g. `@siteping/adapter-prisma` request handlers).\n */\nexport interface SitepingHttpConfig extends SitepingBaseConfig {\n /** HTTP endpoint that receives feedbacks (e.g. '/api/siteping'). */\n endpoint: string;\n /**\n * Convenience auth for HTTP mode — sent as `Authorization: Bearer <apiKey>`\n * on every request to `endpoint`.\n *\n * **WARNING: the widget runs in every visitor's browser, so a static key\n * configured here is public** — anyone can read it from your page source\n * and replay it against your API. Only use `apiKey` for internal tools\n * already behind your own login. On public sites, prefer `headers` with a\n * per-request factory returning a short-lived session token.\n */\n apiKey?: string | undefined;\n /**\n * Extra headers for every HTTP-mode request — a static map, or a factory\n * (sync or async) called once per request (e.g. to fetch a fresh session\n * token). Merged over the widget's generated headers, case-insensitively,\n * so an explicit `Authorization` entry overrides `apiKey`. A factory that\n * throws, rejects, or does not settle within 10 s fails the request like a\n * network error.\n */\n headers?: SitepingHeadersOption | undefined;\n /** Not available in HTTP mode — use either `endpoint` or `store`, never both. */\n store?: never;\n}\n\n/**\n * Store mode — the widget talks to a `SitepingStore` directly in the\n * browser, no server needed (demos, prototypes, localStorage persistence).\n */\nexport interface SitepingStoreConfig extends SitepingBaseConfig {\n /**\n * Direct store for client-side mode. Bypasses HTTP entirely. A send stops\n * waiting on `createFeedback` after 30 s (the call itself cannot be\n * cancelled), so a network-backed store should bound its own calls.\n */\n store: SitepingStore;\n /** Not available in store mode — use either `endpoint` or `store`, never both. */\n endpoint?: never;\n /** HTTP-mode only — meaningless without an `endpoint`. */\n apiKey?: never;\n /** HTTP-mode only — meaningless without an `endpoint`. */\n headers?: never;\n}\n\n/**\n * Configuration options for the Siteping widget.\n *\n * A discriminated union over the two transport modes: pass `endpoint`\n * (HTTP mode, optionally with `apiKey`/`headers`) **or** `store` (direct\n * client-side mode) — never both, never neither. Invalid combinations are\n * compile errors instead of runtime warnings.\n */\nexport type SitepingConfig = SitepingHttpConfig | SitepingStoreConfig;\n\n/** Instance returned by initSiteping() with lifecycle methods. */\nexport interface SitepingInstance {\n /**\n * Release this handle and detach the listeners added through its `on()`.\n * Every `initSiteping()` call returns its own handle over the one widget,\n * which leaves the DOM, all its listeners cleaned up, once the last handle\n * is destroyed. A repeat call is a no-op, and the handle's other methods do\n * nothing afterwards.\n */\n destroy: () => void;\n /** Open the panel programmatically */\n open: () => void;\n /** Close the panel */\n close: () => void;\n /**\n * Reload feedbacks from the server. Resolves once the pins (and the open\n * panel) show the reloaded list; never rejects — like the dashboard's\n * `refresh()`, a failed reload leaves the current view in place.\n */\n refresh: () => Promise<void>;\n /**\n * Scroll the matching annotation into view, pin its highlight, and\n * pulse its marker. Returns `true` when a visible feedback matched the\n * given ID, `false` otherwise (unknown ID, feedback on another URL when\n * `scopeAnnotationsByUrl` filtered it out, or markers not yet loaded).\n *\n * Counterpart to the `deepLink` config option for hosts that prefer to\n * drive focus from JS (e.g., a notification click handler) instead of a\n * URL query parameter.\n */\n focusFeedback: (feedbackId: string) => boolean;\n /** Subscribe to a public widget event */\n on: <K extends keyof SitepingPublicEvents>(event: K, listener: SitepingPublicEventListener<K>) => SitepingUnsubscribe;\n /** Unsubscribe from a public widget event */\n off: <K extends keyof SitepingPublicEvents>(event: K, listener: SitepingPublicEventListener<K>) => void;\n}\n\n/** Listener signature for a single `SitepingPublicEvents` key. */\nexport type SitepingPublicEventListener<K extends keyof SitepingPublicEvents> = (\n ...args: SitepingPublicEvents[K]\n) => void;\n\n/** Disposer returned by `SitepingInstance.on` — call once to detach the listener. */\nexport type SitepingUnsubscribe = () => void;\n\n/** Events exposed to consumers via SitepingInstance.on / .off */\nexport interface SitepingPublicEvents {\n \"feedback:sent\": [FeedbackResponse];\n \"feedback:deleted\": [FeedbackResponse[\"id\"]];\n /** A reply was posted from the panel's discussion thread. */\n \"comment:added\": [CommentResponse];\n /**\n * A feedback API call failed. Same payload contract as\n * `SitepingConfig.onError` — a `SitepingError` subclass in HTTP mode,\n * possibly a raw `Error` in store mode.\n */\n \"feedback:error\": [Error];\n \"panel:open\": [];\n \"panel:close\": [];\n /** The user started drawing an annotation. */\n \"annotation:start\": [];\n /** The user finished drawing an annotation. */\n \"annotation:end\": [];\n}\n\n// ---------------------------------------------------------------------------\n// Feedback\n// ---------------------------------------------------------------------------\n\n/** Single source of truth for feedback types — used by both TS types and Zod schemas. */\nexport const FEEDBACK_TYPES = [\"question\", \"change\", \"bug\", \"other\"] as const;\nexport type FeedbackType = (typeof FEEDBACK_TYPES)[number];\n\n/** Single source of truth for feedback statuses. */\nexport const FEEDBACK_STATUSES = [\"open\", \"in_progress\", \"resolved\", \"wont_fix\"] as const;\nexport type FeedbackStatus = (typeof FEEDBACK_STATUSES)[number];\n\n/**\n * Terminal statuses — the feedback needs no further action. `resolvedAt` is\n * the closure timestamp: set when a feedback enters a closed status, null\n * while it is open or in progress. The derivation happens at the edge (HTTP\n * handler, dashboard) — store adapters persist whatever they are given.\n */\nexport const CLOSED_FEEDBACK_STATUSES = [\"resolved\", \"wont_fix\"] as const;\n/** A terminal status — `resolved` or `wont_fix`. */\nexport type ClosedFeedbackStatus = (typeof CLOSED_FEEDBACK_STATUSES)[number];\n\n/** Non-terminal statuses — the feedback still needs attention. */\nexport const OPEN_FEEDBACK_STATUSES = [\"open\", \"in_progress\"] as const;\n/** A non-terminal status — `open` or `in_progress`. */\nexport type OpenFeedbackStatus = (typeof OPEN_FEEDBACK_STATUSES)[number];\n\n// Adding a fifth status without assigning it to exactly one bucket is a\n// compile error here.\nconst _statusBucketsCoverAll: AssertEqual<OpenFeedbackStatus | ClosedFeedbackStatus, FeedbackStatus> = true;\nvoid _statusBucketsCoverAll;\n\n/** Whether a status is terminal (`resolved` or `wont_fix`). Narrows the status type. */\nexport function isClosedStatus(status: FeedbackStatus): status is ClosedFeedbackStatus {\n return (CLOSED_FEEDBACK_STATUSES as readonly FeedbackStatus[]).includes(status);\n}\n\n/**\n * Page scope returned by `SitepingConfig.getPageScope()`.\n *\n * - `url`: concrete page identifier — usually `window.location.pathname`,\n * used as the strict scope for marker rendering.\n * - `urlPattern`: optional parameterized template (e.g. `/orders/:orderId`)\n * used by the panel's \"this type of page\" filter to group feedbacks across\n * instances of the same page kind.\n */\nexport interface PageScope {\n url: string;\n urlPattern: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/** Input for creating a feedback record in the store. */\nexport interface FeedbackCreateInput {\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n /**\n * Optional parameterized URL template (e.g. `/orders/:orderId`) for the page\n * where the feedback was created. Allows the panel to filter feedbacks by\n * \"this type of page\" across different instances. Null when the host did not\n * provide a `getPageScope` callback or the route has no template.\n */\n urlPattern?: string | null | undefined;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n clientId: string;\n annotations: AnnotationCreateInput[];\n /**\n * Base64 JPEG `data:` URL captured by the widget at submit time.\n *\n * Adapters with a configured `ScreenshotStorage` are expected to upload\n * this and persist the returned URL on `FeedbackRecord.screenshotUrl`.\n * Adapters without storage may persist the data URL inline (memory /\n * localStorage / dev) — the widget then renders it directly.\n */\n screenshotDataUrl?: string | null | undefined;\n /**\n * Where the client's annotation rect sits within the screenshot image,\n * as fractions [0, 1] of the image dimensions. Present when the widget\n * captured context around the drawn rect; null for legacy captures that\n * were cropped exactly to the rect (dashboards then render the image\n * without an overlay).\n */\n screenshotRegion?: ScreenshotRegion | null | undefined;\n /**\n * Optional console + failed-network snapshot captured by the widget when\n * `SitepingConfig.captureDiagnostics` is enabled. Stored as JSON on\n * `FeedbackRecord.diagnostics` so reviewers can replay the context.\n */\n diagnostics?: DiagnosticsSnapshot | null | undefined;\n}\n\n/** Input for a single annotation when creating a feedback. */\nexport interface AnnotationCreateInput {\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId?: string | undefined;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n /**\n * Semantic anchor identifier from the closest ancestor's `data-feedback-anchor`\n * attribute. When set, this is the most stable re-anchoring signal because\n * hosts deliberately place these on layout/section roots that survive DOM\n * refactors and viewport changes. Null when no semantic ancestor exists.\n */\n anchorKey?: string | null | undefined;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n/** Query parameters for fetching feedbacks. */\nexport interface FeedbackQuery {\n projectName: string;\n type?: FeedbackType | undefined;\n /** Exact single-status filter. For \"any of a set\" (bucket) semantics, use `statuses`. */\n status?: FeedbackStatus | undefined;\n /**\n * Filter to feedbacks whose status is any of the listed values — bucket\n * semantics used by the panel's binary tabs (e.g. \"Open\" passes\n * `[\"open\", \"in_progress\"]`). When both `status` and `statuses` are set,\n * `statuses` wins. An empty array is treated as absent (no status filter).\n */\n statuses?: readonly FeedbackStatus[] | undefined;\n search?: string | undefined;\n page?: number | undefined;\n /**\n * Page size. Defaults to `DEFAULT_PAGE_LIMIT` (50) and is capped at\n * `MAX_PAGE_LIMIT` (100) — `clampPagination` implements both, and the\n * conformance suite checks them.\n */\n limit?: number | undefined;\n /**\n * Filter to feedbacks created on this exact URL (path). Used by the panel's\n * \"this page\" filter and by the markers loader to keep page scopes isolated.\n */\n url?: string | undefined;\n /**\n * Filter to feedbacks created on this URL pattern (e.g. `/orders/:orderId`).\n * Used by the panel's \"this type of page\" filter to group feedbacks across\n * different concrete instances of the same template.\n */\n urlPattern?: string | undefined;\n}\n\n/**\n * Update payload for patching a feedback.\n *\n * A discriminated union encoding the closure invariant: a feedback entering\n * a closed status carries its closure timestamp, an open one carries `null`.\n * `{ status: \"resolved\", resolvedAt: null }` is a compile error instead of a\n * silent data bug. Build it from a plain `FeedbackStatus` with\n * {@link toFeedbackUpdate}.\n */\nexport type FeedbackUpdateInput =\n | { status: OpenFeedbackStatus; resolvedAt: null }\n | { status: ClosedFeedbackStatus; resolvedAt: Date };\n\n/**\n * Derive the {@link FeedbackUpdateInput} for a status change — the closure\n * timestamp is stamped for closed statuses and cleared otherwise. This is\n * the edge derivation described on {@link CLOSED_FEEDBACK_STATUSES}; store\n * adapters persist the result verbatim.\n */\nexport function toFeedbackUpdate(status: FeedbackStatus, closedAt: Date = new Date()): FeedbackUpdateInput {\n return isClosedStatus(status) ? { status, resolvedAt: closedAt } : { status, resolvedAt: null };\n}\n\n/** A persisted feedback record returned by the store. */\nexport interface FeedbackRecord {\n id: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n projectName: string;\n url: string;\n /**\n * Parameterized URL template the feedback was created on.\n * Null for legacy records or hosts without `getPageScope`.\n */\n urlPattern: string | null;\n authorName: string;\n authorEmail: string;\n viewport: string;\n userAgent: string;\n clientId: string;\n resolvedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n annotations: AnnotationRecord[];\n /**\n * Discussion thread, oldest first. A store that implements\n * `SitepingStore.addComment` returns it on every record; a store without\n * comments may leave it out, which reads as an empty thread (HTTP handlers\n * send `[]`).\n */\n comments?: CommentRecord[] | undefined;\n /**\n * URL the widget renders as `<img src>`. Either an `https://...` from a\n * configured `ScreenshotStorage`, or a `data:image/jpeg;base64,...` URL\n * inline-persisted by adapters without storage. Null when no screenshot\n * was captured (legacy records, capture failed, or host disabled it).\n */\n screenshotUrl: string | null;\n /**\n * Annotation rect position within the screenshot image, as fractions of\n * its dimensions. Null for legacy captures cropped exactly to the rect.\n */\n screenshotRegion: ScreenshotRegion | null;\n /**\n * Console + failed-network snapshot captured at submit time. Null when\n * diagnostics weren't enabled on the widget side.\n */\n diagnostics: DiagnosticsSnapshot | null;\n}\n\n/** A persisted annotation record returned by the store. */\nexport interface AnnotationRecord {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n /**\n * Semantic anchor identifier from `data-feedback-anchor`. Null for legacy\n * annotations or those drawn outside any anchored region.\n */\n anchorKey: string | null;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: Date;\n}\n\n// ---------------------------------------------------------------------------\n// Comments — the discussion thread of a feedback\n// ---------------------------------------------------------------------------\n\n/**\n * Who wrote a comment: `client` — the reviewer on the site (the widget) —\n * or `team` — the project side (the dashboard). HTTP handlers stamp\n * `client` unless the server's access policy vouches for the caller, so a\n * visitor cannot pose as the team.\n */\nexport const COMMENT_AUTHOR_ROLES = [\"client\", \"team\"] as const;\nexport type CommentAuthorRole = (typeof COMMENT_AUTHOR_ROLES)[number];\n\n/** Longest comment `body` the HTTP API accepts — the cap of a feedback `message`. */\nexport const COMMENT_BODY_MAX_LENGTH = 5000;\n\n/**\n * Most `client` comments one thread holds. List responses embed whole\n * threads, which are not paginated, so this bounds what one feedback weighs\n * however much a public endpoint is spammed. `team` comments — a role only\n * the access policy grants — neither count nor meet it: a thread spammed\n * full still takes the team's answer. Stores enforce it with\n * `StoreLimitError`; a store without an atomic primitive may overshoot it by\n * the posts that race the last free slot.\n */\nexport const MAX_COMMENTS_PER_FEEDBACK = 100;\n\n/** A persisted comment returned by the store. */\nexport interface CommentRecord {\n id: string;\n /** The feedback whose thread holds this comment. */\n feedbackId: string;\n body: string;\n authorName: string;\n /**\n * Author email, or `\"\"` when the author has none on file (a dashboard\n * user). HTTP handlers redact it exactly like `FeedbackRecord.authorEmail`.\n */\n authorEmail: string;\n authorRole: CommentAuthorRole;\n /** Client-generated id — `addComment` is idempotent on it, so a retried post never duplicates a reply. */\n clientId: string;\n createdAt: Date;\n}\n\n/** Input of `SitepingStore.addComment`. */\nexport interface CommentCreateInput {\n body: string;\n authorName: string;\n authorEmail: string;\n authorRole: CommentAuthorRole;\n clientId: string;\n}\n\n/**\n * Body of a comment `POST`. `feedbackId` is what tells it apart from a\n * feedback submission on the same endpoint; `projectName` scopes the write\n * to that project's feedbacks.\n */\nexport interface CommentPayload extends CommentCreateInput {\n projectName: string;\n feedbackId: string;\n}\n\n/** Body of a comment `DELETE` — `commentId` is what tells it apart from a feedback delete. */\nexport interface CommentDeletePayload {\n projectName: string;\n feedbackId: string;\n commentId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Store errors — throw these from adapter implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a record is not found during update or delete.\n *\n * Handlers translate this to HTTP 404. Adapters MUST throw this (not\n * ORM-specific errors) so the handler layer remains ORM-agnostic.\n */\nexport class StoreNotFoundError extends Error {\n readonly code = \"STORE_NOT_FOUND\" as const;\n constructor(message = \"Record not found\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"StoreNotFoundError\";\n }\n}\n\n/**\n * Thrown when a unique constraint is violated (e.g. duplicate `clientId`).\n *\n * Handlers use this to return the existing record instead of failing.\n */\nexport class StoreDuplicateError extends Error {\n readonly code = \"STORE_DUPLICATE\" as const;\n constructor(message = \"Duplicate record\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"StoreDuplicateError\";\n }\n}\n\n/**\n * Thrown when a store accepts a mutation but cannot persist it — e.g.\n * `localStorage` is full (QuotaExceededError). Adapters MUST throw this rather\n * than swallow the failure, so callers learn the write was lost instead of\n * seeing a phantom success.\n */\nexport class StorePersistenceError extends Error {\n readonly code = \"STORE_PERSISTENCE\" as const;\n constructor(message = \"Failed to persist store mutation\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"StorePersistenceError\";\n }\n}\n\n/**\n * Thrown when a write would break a bound of the store contract — a\n * `client` comment on a thread already holding `MAX_COMMENTS_PER_FEEDBACK`\n * of them. Handlers translate this to HTTP 409.\n */\nexport class StoreLimitError extends Error {\n readonly code = \"STORE_LIMIT\" as const;\n constructor(message = \"Store limit reached\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"StoreLimitError\";\n }\n}\n\n/**\n * Thrown when a value is longer than the store can hold, though the HTTP\n * validation accepts it — a MySQL schema that predates the columns `sync`\n * now sizes from the HTTP caps keeps Prisma's `VARCHAR(191)` for a plain\n * `String`. Handlers translate this to HTTP 422: the submission can never be\n * stored as it is, so a client must not retry it.\n */\nexport class StoreValueTooLongError extends Error {\n readonly code = \"STORE_VALUE_TOO_LONG\" as const;\n constructor(message = \"Value too long for the store\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"StoreValueTooLongError\";\n }\n}\n\n/** Shape of any ORM error that carries a Prisma-style `code` field. */\ntype CodedError<C extends string = string> = { code: C };\n\nfunction hasErrorCode<C extends string>(error: unknown, code: C): error is CodedError<C> {\n return hasOwn(error, \"code\") && error.code === code;\n}\n\n/**\n * Type guard — works for `StoreNotFoundError` and ORM-specific equivalents\n * (e.g. Prisma P2025). Matches on the stable `code` too, for the same\n * cross-bundle reason as {@link isStorePersistence}.\n */\nexport function isStoreNotFound(error: unknown): error is StoreNotFoundError | CodedError<\"STORE_NOT_FOUND\" | \"P2025\"> {\n if (error instanceof StoreNotFoundError) return true;\n // Another bundle's copy of core, or Prisma's P2025 (backwards compat)\n return hasErrorCode(error, \"STORE_NOT_FOUND\") || hasErrorCode(error, \"P2025\");\n}\n\n/**\n * Type guard — works for `StoreDuplicateError` and ORM-specific equivalents\n * (e.g. Prisma P2002). Matches on the stable `code` too, for the same\n * cross-bundle reason as {@link isStorePersistence}.\n */\nexport function isStoreDuplicate(\n error: unknown,\n): error is StoreDuplicateError | CodedError<\"STORE_DUPLICATE\" | \"P2002\"> {\n if (error instanceof StoreDuplicateError) return true;\n // Another bundle's copy of core, or Prisma's P2002 (backwards compat)\n return hasErrorCode(error, \"STORE_DUPLICATE\") || hasErrorCode(error, \"P2002\");\n}\n\n/**\n * Type guard for `StorePersistenceError`. Matches on the stable `code` field\n * in addition to `instanceof`: every consumer package bundles its own copy of\n * core (tsup `noExternal`), so an instance thrown by one package fails an\n * `instanceof` check against another package's class identity.\n */\nexport function isStorePersistence(error: unknown): error is StorePersistenceError | CodedError<\"STORE_PERSISTENCE\"> {\n if (error instanceof StorePersistenceError) return true;\n return hasErrorCode(error, \"STORE_PERSISTENCE\");\n}\n\n/** Type guard for `StoreLimitError`, matching on `code` for the same cross-bundle reason as {@link isStorePersistence}. */\nexport function isStoreLimit(error: unknown): error is StoreLimitError | CodedError<\"STORE_LIMIT\"> {\n if (error instanceof StoreLimitError) return true;\n return hasErrorCode(error, \"STORE_LIMIT\");\n}\n\n/** Type guard for `StoreValueTooLongError`, matching on `code` for the same cross-bundle reason as {@link isStorePersistence}. */\nexport function isStoreValueTooLong(\n error: unknown,\n): error is StoreValueTooLongError | CodedError<\"STORE_VALUE_TOO_LONG\"> {\n if (error instanceof StoreValueTooLongError) return true;\n return hasErrorCode(error, \"STORE_VALUE_TOO_LONG\");\n}\n\n// ---------------------------------------------------------------------------\n// Store helpers — shared conversion logic for adapters\n// ---------------------------------------------------------------------------\n\n/** Flatten a widget `AnnotationPayload` (nested anchor + rect) into a flat `AnnotationCreateInput`. */\nexport function flattenAnnotation(ann: AnnotationPayload): AnnotationCreateInput {\n return {\n cssSelector: ann.anchor.cssSelector,\n xpath: ann.anchor.xpath,\n textSnippet: ann.anchor.textSnippet,\n elementTag: ann.anchor.elementTag,\n elementId: ann.anchor.elementId,\n textPrefix: ann.anchor.textPrefix,\n textSuffix: ann.anchor.textSuffix,\n fingerprint: ann.anchor.fingerprint,\n neighborText: ann.anchor.neighborText,\n anchorKey: ann.anchor.anchorKey ?? null,\n xPct: ann.rect.xPct,\n yPct: ann.rect.yPct,\n wPct: ann.rect.wPct,\n hPct: ann.rect.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/**\n * Outcome of `SitepingStore.createFeedbackIfAbsent` — the record plus whether\n * this very call inserted it.\n */\nexport interface FeedbackCreateOutcome {\n feedback: FeedbackRecord;\n /**\n * `true` when this call inserted the record, `false` when a record with the\n * same `clientId` already existed and is returned instead (a replay, or a\n * concurrent request that won the race).\n */\n created: boolean;\n}\n\n/**\n * Outcome of `SitepingStore.addCommentIfAbsent` — the comment plus whether\n * this very call inserted it.\n */\nexport interface CommentCreateOutcome {\n comment: CommentRecord;\n /**\n * `true` when this call inserted the comment, `false` when a comment with\n * the same `clientId` already existed and is returned instead (a replay, or\n * a concurrent request that won the race).\n */\n created: boolean;\n}\n\n/** Paginated result returned by `SitepingStore.getFeedbacks`. */\nexport interface FeedbackPage {\n feedbacks: FeedbackRecord[];\n total: number;\n}\n\n/**\n * Abstract storage interface for Siteping.\n *\n * Any adapter (Prisma, Drizzle, raw SQL, localStorage, etc.) implements this\n * interface. The HTTP handler and widget `StoreClient` operate against\n * `SitepingStore`, decoupled from the storage backend.\n *\n * ## Error contract\n *\n * - **`updateFeedback` / `deleteFeedback`**: throw `StoreNotFoundError` when\n * the record does not exist.\n * - **`createFeedback`**: throw `StoreDuplicateError` on a duplicate\n * `clientId` — never return the existing record, so a caller always knows\n * whether this call inserted. The optional `createFeedbackIfAbsent`\n * reports the existing record instead, as `{ created: false }`.\n * - **All mutations**: when a write is accepted but cannot be persisted\n * (e.g. storage quota), throw `StorePersistenceError` instead of reporting\n * a phantom success. Detect it with `isStorePersistence`.\n * - Other methods should not throw on empty results — return empty arrays or `null`.\n */\nexport interface SitepingStore {\n /** Create a feedback with its annotations. Throws `StoreDuplicateError` when a feedback with the same `clientId` exists, `StorePersistenceError` when the write cannot be persisted. */\n createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;\n /** Paginated query with optional filters. Returns empty array (not error) when no results. */\n getFeedbacks(query: FeedbackQuery): Promise<FeedbackPage>;\n /** Lookup by client-generated UUID. Returns `null` (not error) when not found. */\n findByClientId(clientId: string): Promise<FeedbackRecord | null>;\n /** Update status/resolvedAt. Throws `StoreNotFoundError` if `id` does not exist, `StorePersistenceError` when the write cannot be persisted. */\n updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;\n /** Delete a single record. Throws `StoreNotFoundError` if `id` does not exist, `StorePersistenceError` when the write cannot be persisted. */\n deleteFeedback(id: string): Promise<void>;\n /** Bulk delete all feedbacks for a project. No-op (not error) if none exist. Throws `StorePersistenceError` when the write cannot be persisted. */\n deleteAllFeedbacks(projectName: string): Promise<void>;\n /**\n * Return `true` when the record with `id` belongs to `projectName`, `false`\n * otherwise (including when it does not exist). HTTP handlers use it to\n * reject cross-project PATCH/DELETE requests, which address records by id.\n */\n verifyProjectOwnership(id: string, projectName: string): Promise<boolean>;\n /**\n * Optional — `createFeedback` that reports whether this call inserted the\n * record (`created: true`) or found an existing one with the same\n * `clientId` (`created: false`). The dedup check and the insert must be\n * atomic, like `createFeedback`'s: of N concurrent calls with the same\n * `clientId`, exactly one may report `created: true`, and all must return\n * that same record. `createCollectionStore` guarantees this within one\n * store instance by serializing its mutations; stores shared across\n * processes need an atomic backend primitive (unique constraint,\n * transaction, compare-and-set).\n *\n * HTTP handlers prefer it over `createFeedback`: a replayed or racing\n * submission resolves to the stored record in one atomic call, without a\n * `StoreDuplicateError` to catch and a lookup to follow it. Without it,\n * they fall back to `createFeedback` and recover its duplicate error.\n */\n createFeedbackIfAbsent?(data: FeedbackCreateInput): Promise<FeedbackCreateOutcome>;\n /**\n * Optional — append a comment to the thread of feedback `feedbackId` and\n * return it. Idempotent on `data.clientId`: when a comment with that\n * `clientId` exists, on any thread, return it unchanged (HTTP handlers\n * refuse a replay aimed at another thread).\n *\n * Throws `StoreNotFoundError` when the feedback does not exist,\n * `StoreLimitError` when a `client` comment meets a thread already holding\n * `MAX_COMMENTS_PER_FEEDBACK` of them (a `team` comment is never refused\n * for it), `StorePersistenceError` when the\n * write cannot be persisted. A comment is not a change of the feedback\n * itself: its `updatedAt` stays as it was.\n *\n * A store that implements it returns every feedback's thread on\n * `FeedbackRecord.comments`, oldest first, and deletes a thread with its\n * feedback. A store without it (and `deleteComment`) has no threads:\n * HTTP handlers serve empty threads and answer comment writes with 501.\n */\n addComment?(feedbackId: string, data: CommentCreateInput): Promise<CommentRecord>;\n /**\n * Optional, alongside `addComment` — `addComment` that reports whether this\n * call inserted the comment (`created: true`) or found one with the same\n * `clientId` (`created: false`). Same errors as `addComment`, and the dedup\n * check and the insert must be atomic like `createFeedbackIfAbsent`'s: of N\n * concurrent calls with the same `clientId`, exactly one may report\n * `created: true`, and all must return that same comment.\n *\n * HTTP handlers need it to fire the `comment.created` webhook and the\n * `onCommentCreated` hook exactly once per reply — a retried post returns\n * the stored comment, which `addComment` alone cannot tell from a new one.\n */\n addCommentIfAbsent?(feedbackId: string, data: CommentCreateInput): Promise<CommentCreateOutcome>;\n /**\n * Optional — delete comment `commentId` from the thread of `feedbackId`.\n * Scoped to that thread, which the caller was authorized for: throws\n * `StoreNotFoundError` when the feedback does not exist or the comment is\n * not on its thread, `StorePersistenceError` when the write cannot be\n * persisted.\n */\n deleteComment?(feedbackId: string, commentId: string): Promise<void>;\n}\n\n/** Payload sent from the widget to the server when submitting feedback. */\nexport interface FeedbackPayload {\n projectName: string;\n type: FeedbackType;\n message: string;\n url: string;\n /**\n * Parameterized URL template (e.g. `/orders/:orderId`) supplied by\n * `SitepingConfig.getPageScope()`. Null when the host did not provide one.\n */\n urlPattern?: string | null | undefined;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n annotations: AnnotationPayload[];\n /** Client-generated UUID for deduplication */\n clientId: string;\n /**\n * Base64 JPEG `data:` URL of the annotated area. Captured by the widget\n * when `enableScreenshot: true` is set in `SitepingConfig`. Null when\n * disabled or when capture failed silently.\n */\n screenshotDataUrl?: string | null | undefined;\n /**\n * Annotation rect position within the screenshot image — see\n * `ScreenshotRegion`. Null/absent when no screenshot was captured or the\n * capture predates contextual framing.\n */\n screenshotRegion?: ScreenshotRegion | null | undefined;\n /**\n * Snapshot of the last few console messages and failed network requests\n * captured at submit time when `captureDiagnostics` is enabled.\n */\n diagnostics?: DiagnosticsSnapshot | null | undefined;\n}\n\n/** Single source of truth for console diagnostic severity levels. */\nexport const CONSOLE_DIAGNOSTIC_LEVELS = [\"log\", \"info\", \"warn\", \"error\"] as const;\n/** Severity levels persisted in `ConsoleDiagnosticEntry`. */\nexport type ConsoleDiagnosticLevel = (typeof CONSOLE_DIAGNOSTIC_LEVELS)[number];\n\n/** A single console entry captured by `ConsoleBuffer`. */\nexport interface ConsoleDiagnosticEntry {\n level: ConsoleDiagnosticLevel;\n /** ISO 8601 timestamp captured at log time. */\n timestamp: string;\n /** Best-effort string representation of the original console args. */\n message: string;\n}\n\n/** A single failed network request captured by `NetworkBuffer`. */\nexport interface NetworkDiagnosticEntry {\n url: string;\n method: string;\n /** HTTP status; 0 when the request never reached the server. */\n status: number;\n /** End-to-end duration in ms. */\n durationMs: number;\n /** ISO 8601 timestamp at the moment the request was initiated. */\n timestamp: string;\n}\n\n/**\n * Diagnostics captured by the widget when `captureDiagnostics` is enabled.\n *\n * Both arrays are bounded (default: 50 console / 20 network). Adapters that\n * support diagnostics should persist this as a JSON blob alongside the\n * feedback so reviewers can replay the context that led to the report.\n */\nexport interface DiagnosticsSnapshot {\n console: ConsoleDiagnosticEntry[];\n network: NetworkDiagnosticEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Annotation — multi-selector anchoring (Hypothesis / W3C Web Annotation)\n// ---------------------------------------------------------------------------\n\n/**\n * Length caps for `AnchorData.elementTag` / `elementId`, shared by the widget\n * (which bounds what it captures) and the HTTP adapter (which rejects a longer\n * tag and drops a longer id) so the two can't drift. 191 fits Prisma's default `String` column\n * on MySQL (`VARCHAR(191)`); real tag names and ids are far shorter.\n */\nexport const ANCHOR_ELEMENT_TAG_MAX = 191;\nexport const ANCHOR_ELEMENT_ID_MAX = 191;\n\n/** DOM anchoring data for re-attaching annotations to page elements. */\nexport interface AnchorData {\n /**\n * CSS selector generated by @medv/finder — primary anchor. Inside open\n * shadow roots: one selector per tree, outermost host first, joined by\n * `\" >>> \"` (e.g. `\"#pricing >>> .plan-title\"`).\n */\n cssSelector: string;\n /**\n * XPath — fallback 1. Inside a shadow root it is relative to that root\n * (`./…`) and informational only: XPath cannot enter shadow trees.\n */\n xpath: string;\n /** First ~120 chars of element innerText — empty string if none */\n textSnippet: string;\n /** Tag name for validation (e.g. \"DIV\", \"SECTION\") */\n elementTag: string;\n /** Element id attribute if available — most stable */\n elementId?: string | undefined;\n /** ~32 chars of text before this element in document flow (disambiguation) */\n textPrefix: string;\n /** ~32 chars of text after this element in document flow (disambiguation) */\n textSuffix: string;\n /** Structural fingerprint: \"childCount:siblingIdx:attrHash\" */\n fingerprint: string;\n /** Text content of adjacent sibling elements (context) */\n neighborText: string;\n /**\n * Semantic anchor identifier from the closest ancestor's `data-feedback-anchor`\n * attribute. When set, this is the highest-priority re-anchoring signal —\n * hosts deliberately place these on layout/section roots that survive\n * viewport changes and DOM refactors.\n */\n anchorKey?: string | null | undefined;\n}\n\n/**\n * Where the client's annotation rect sits within the captured screenshot,\n * as fractions [0, 1] of the image dimensions. The widget captures context\n * around the drawn rect and records the rect's position here so dashboards\n * can re-render the annotation on top of the image. Survives downscaling\n * (fractions are resolution-independent).\n */\nexport interface ScreenshotRegion {\n /** X offset of the rect as fraction of image width — [0, 1] */\n xPct: number;\n /** Y offset of the rect as fraction of image height — [0, 1] */\n yPct: number;\n /** Rect width as fraction of image width — [0, 1] */\n wPct: number;\n /** Rect height as fraction of image height — [0, 1] */\n hPct: number;\n}\n\n/** Drawn rectangle coordinates as percentages relative to the anchor element. */\nexport interface RectData {\n /** X offset as fraction of anchor element width — must be in range [0, 1] */\n xPct: number;\n /** Y offset as fraction of anchor element height — must be in range [0, 1] */\n yPct: number;\n /** Width as fraction of anchor element width — must be in range [0, 1] */\n wPct: number;\n /** Height as fraction of anchor element height — must be in range [0, 1] */\n hPct: number;\n}\n\n/** Annotation data sent as part of a feedback submission. */\nexport interface AnnotationPayload {\n anchor: AnchorData;\n rect: RectData;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n// ---------------------------------------------------------------------------\n// API responses\n// ---------------------------------------------------------------------------\n\n/**\n * Feedback record as returned by the API — derived from\n * {@link FeedbackRecord}: dates are serialized to ISO strings and `clientId`\n * is omitted (server-side dedup concern, never exposed on the wire), on the\n * record and on each comment of its thread. Adding a field to\n * `FeedbackRecord` updates this type automatically.\n *\n * Note: `authorEmail` may be an empty string — HTTP adapters redact it for\n * unauthenticated requests; the full value requires a Bearer-authenticated\n * request.\n */\nexport type FeedbackResponse = Prettify<\n Serialized<Omit<FeedbackRecord, \"clientId\" | \"comments\">> & {\n /** The thread, oldest first — always sent by `@siteping/server`, absent from servers that predate comments. */\n comments?: CommentResponse[] | undefined;\n /**\n * What the requester may do with this feedback — always sent by\n * `@siteping/server`. Absent from servers that predate it, and in store\n * mode: nothing is refused then.\n */\n permissions?: FeedbackPermissions | undefined;\n }\n>;\n\n/**\n * What a requester may do with one feedback, as the server's access policy\n * decides — so clients hide the actions it would refuse. The server still\n * enforces every one of them.\n */\nexport interface FeedbackPermissions {\n /** Change its status: resolve, reopen, … */\n canChangeStatus: boolean;\n /** Delete it. */\n canDelete: boolean;\n /** Reply in its thread. */\n canComment: boolean;\n /** Delete replies from its thread. */\n canDeleteComment: boolean;\n}\n\n/**\n * What a requester may do with one comment of a thread, as the server's\n * access policy decides — finer than the feedback's `canDeleteComment`, which\n * covers the whole thread: a policy that lets authors delete their own\n * replies only grants it comment by comment.\n */\nexport interface CommentPermissions {\n /** Delete it. */\n canDelete: boolean;\n}\n\n/** What a requester may do with a whole project, sent with each list. */\nexport interface FeedbackListPermissions {\n /** Delete every feedback of the project at once. */\n canDeleteAll: boolean;\n}\n\n/**\n * Annotation record as returned by the API — {@link AnnotationRecord} with\n * `createdAt` serialized to an ISO string.\n */\nexport type AnnotationResponse = Prettify<Serialized<AnnotationRecord>>;\n\n/**\n * Comment as returned by the API — {@link CommentRecord} with `createdAt`\n * serialized and `clientId` omitted, like on `FeedbackResponse`. Its\n * `authorEmail` is redacted on the same terms as the feedback's.\n */\nexport type CommentResponse = Prettify<\n Serialized<Omit<CommentRecord, \"clientId\">> & {\n /**\n * What the requester may do with this comment — always sent by\n * `@siteping/server`. Absent from servers that predate it, and in store\n * mode: the feedback's `canDeleteComment` decides then.\n */\n permissions?: CommentPermissions | undefined;\n }\n>;\n\n/**\n * Version of the HTTP protocol `@siteping/server` speaks, sent as `protocol`\n * on every list response. It rises with each addition a client may rely on —\n * a route, a field, a status — so a client checks `protocol >= N` before\n * using what protocol N added, instead of probing for fields. A server that\n * predates it sends none: read that as 0. The OpenAPI document shipped as\n * `@siteping/server/openapi.json` describes this version.\n */\nexport const SITEPING_PROTOCOL_VERSION = 1;\n\n/** What the store behind an endpoint supports — advertised on every list response. */\nexport interface SitepingCapabilities {\n /** Whether comments can be posted: the store implements `addComment`. */\n comments: boolean;\n /**\n * Whether comments can be deleted: the store implements `deleteComment`.\n * Sent by `@siteping/server`; a client that does not delete (the widget)\n * leaves it out.\n */\n deleteComments?: boolean | undefined;\n}\n\n/** Paginated `FeedbackResponse` shape returned by the API. */\nexport interface FeedbackResponseList {\n feedbacks: FeedbackResponse[];\n total: number;\n /** The server's {@link SITEPING_PROTOCOL_VERSION} — absent from servers that predate it: read that as 0. */\n protocol?: number | undefined;\n /** Always sent by `@siteping/server` — absent from servers that predate it. */\n capabilities?: SitepingCapabilities | undefined;\n /** Always sent by `@siteping/server` — absent from servers that predate it. */\n permissions?: FeedbackListPermissions | undefined;\n}\n","import type { FeedbackRecord } from \"./types.js\";\n\n/**\n * `url` resolved against `base`, when the result is an http(s) URL. A\n * record's `url` is client-supplied: without the scheme allowlist, a crafted\n * feedback (`javascript:…`, `data:…`) would turn into a live link.\n */\nexport function parseHttpUrl(url: string, base?: string): URL | null {\n try {\n const parsed = new URL(url, base);\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\" ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether a screenshot URL is safe to render as an `<img src>`, in the\n * widget's panel and the dashboard alike. A store can hold any string there\n * (a direct store caller, a custom `ScreenshotStorage`, a migrated database):\n * a `javascript:`, `data:text/html` or `data:image/svg+xml` URL — SVG can\n * carry external `<image href>` references — or an arbitrary host would make\n * the viewer fetch from it, leaking their IP and user agent. Browsers never\n * run scripts from an `<img>`, but the fetch itself still happens.\n */\nexport function isSafeImageUrl(url: string): boolean {\n // The formats the widget captures; SVG is rarely a screenshot anyway.\n if (/^data:image\\/(jpeg|png|webp);/i.test(url)) return true;\n // Remote storage over https (S3, R2…). Other http: URLs are refused: the\n // viewer typically runs over https, where mixed content is blocked anyway.\n if (/^https:\\/\\//i.test(url)) return true;\n return isLoopbackHttp(url);\n}\n\n/**\n * Plain-http URL on this machine — a dev stack's local object storage (MinIO\n * on `localhost:9000`, localstack) behind a `ScreenshotStorage`. Nothing\n * leaves the machine, so there is no IP/UA/Referer leak to defend against.\n * Parsed rather than pattern-matched, so `localhost.evil.com` or\n * `localhost@evil.com` never pass for loopback.\n */\nfunction isLoopbackHttp(url: string): boolean {\n try {\n const { protocol, hostname } = new URL(url);\n return (\n protocol === \"http:\" &&\n (hostname === \"localhost\" || hostname.endsWith(\".localhost\") || hostname === \"127.0.0.1\" || hostname === \"[::1]\")\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Link that opens a feedback's page with the widget focused on it —\n * `?<param>=<id>`, read by the widget's `deepLink` option. Relative record\n * URLs (the widget stores `location.pathname` by default) resolve against\n * `base`. Null when there is no safe http(s) target.\n */\nexport function buildDeepLink(record: Pick<FeedbackRecord, \"id\" | \"url\">, param: string, base?: string): string | null {\n const url = parseHttpUrl(record.url, base);\n if (!url) return null;\n url.searchParams.set(param, record.id);\n return url.toString();\n}\n","/**\n * The one email pattern every Siteping surface validates against.\n *\n * The widget's identity modal and the HTTP handler's schema used to disagree\n * (a permissive regex on one side, an ASCII-only default on the other), so an\n * address the modal accepted — and persisted in localStorage for good — came\n * back as a 400 on every submission afterwards. One pattern, imported on both\n * sides, makes that drift impossible.\n *\n * Deliberately Unicode-aware: internationalised local parts and domains\n * (`françois@exemple.fr`, `user@münchen.de`, `user@हिंदी.भारत`) are real\n * addresses and common in the audience this widget serves. Combining marks\n * (`\\p{M}`) are allowed alongside letters — scripts like Devanagari need them,\n * and NFD-composed input spells `ç` as `c` + U+0327 — except as the first\n * character of a domain label. Structure follows the usual rules — a local\n * part of 1 to 64 characters with no leading, trailing or doubled dot,\n * dot-separated domain labels of at most 63 characters that neither start nor\n * end with a hyphen, and a final label of at least two characters.\n */\nexport const EMAIL_PATTERN =\n /^(?!\\.)(?!.*\\.\\.)[\\p{L}\\p{M}\\p{N}!#$%&'*+/=?^_`{|}~.-]{0,63}[\\p{L}\\p{M}\\p{N}!#$%&'*+/=?^_`{|}~-]@(?:[\\p{L}\\p{N}](?:[\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}])?\\.)+[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N}-]{0,61}[\\p{L}\\p{M}\\p{N}]$/u;\n\n/** Whether `value` is an email address Siteping accepts — see {@link EMAIL_PATTERN}. */\nexport function isValidEmail(value: string): boolean {\n return EMAIL_PATTERN.test(value);\n}\n","/**\n * Generic i18n machinery shared by the widget and the dashboard.\n *\n * Each package owns its `Translations` interface (their key sets differ) and\n * its locale dictionaries; everything else — locale normalization, the\n * custom-locale registry, lazy loading of built-ins, the translate-function\n * factory, interpolation — is identical and lives here once.\n *\n * The `loaders` map is typed `Record<Exclude<BuiltinLocale, \"en\">, …>`: the\n * moment a locale code is added to {@link BUILTIN_LOCALES}, every consuming\n * package fails to compile until its loader entry (and therefore its\n * dictionary file) exists. Adding a locale cannot silently fall back to\n * English anymore.\n */\n\nimport type { BuiltinLocale } from \"./types.js\";\n\n/**\n * Lazy dictionary loaders for every non-English built-in locale. Use static\n * `() => import(\"./xx.js\").then((m) => m.xx)` thunks — bundlers keep\n * emitting one chunk per locale, and only the requested one ships.\n */\nexport type LocaleLoaders<T> = Record<Exclude<BuiltinLocale, \"en\">, () => Promise<T>>;\n\n/** Translate function over the message catalog `T`. */\nexport type TranslateFunction<T> = (key: keyof T & string) => string;\n\n/** The i18n API returned by {@link createI18n}. */\nexport interface I18n<T> {\n /**\n * Create a translation function for the given locale.\n *\n * Locale resolution, per key: `registerLocale` override > built-in\n * dictionary of the language > English fallback.\n * Non-English built-in locales are lazy-loaded via `loadLocale` — call\n * `await loadLocale(locale)` at init if you want the UI to render in the\n * target language immediately; until the dictionary lands, keys resolve\n * to English.\n */\n createT(locale: string): TranslateFunction<T>;\n /**\n * Dynamically import a built-in locale (once) and register it. Returns the\n * locale as `createT` resolves it — the built-in dictionary with any\n * `registerLocale` overrides on top — or `null` for a code that is neither\n * built in nor registered. A custom code never hits a loader: it comes\n * back as registered, so a partial custom dictionary comes back partial.\n */\n loadLocale(locale: string): Promise<Partial<T> | null>;\n /**\n * Register translations at runtime, for a new locale or over a built-in\n * one. Partial dictionaries are welcome: the keys given override the\n * built-in dictionary (whenever it loads), every other key keeps its\n * built-in value, then falls back to English — so overriding a single\n * string never requires copying the whole catalog. Calls for the same\n * code accumulate.\n */\n registerLocale(code: string, translations: Partial<T>): void;\n}\n\n/**\n * Normalise a locale tag down to the base language used for dictionary\n * lookups — BCP-47 `fr-FR` and backend-style `fr_FR` both resolve to `fr`.\n */\nfunction normaliseLang(locale: string): string {\n return (locale.split(/[-_]/)[0] ?? locale).toLowerCase();\n}\n\n/** `locale` with `_` read as `-`, canonicalised — `undefined` when `Intl` rejects it. */\nfunction canonicalTag(locale: string): string | undefined {\n try {\n return Intl.getCanonicalLocales(locale.replace(/_/g, \"-\"))[0];\n } catch {\n // RangeError: not a well-formed language tag\n return undefined;\n }\n}\n\n/**\n * `locale` as `Intl` accepts it. Locale options take any string, and a\n * backend-style tag (`fr_FR` from PHP / WordPress) makes every `Intl`\n * constructor and `toLocale*` call throw a RangeError: `_` reads as `-`, and\n * a tag `Intl` still rejects falls back to English, as `createT` does.\n * Silent, so it is safe on a render path; see `canonicalizeLocale` for the\n * one-time, warning variant.\n */\nexport function intlLocale(locale: string): string {\n return canonicalTag(locale) ?? \"en\";\n}\n\n/**\n * The host's locale tag as `Intl` accepts it. `SitepingLocale` takes any\n * string, and PHP / WordPress hand out POSIX-style tags (`fr_FR`, `pt_BR`)\n * that every `Intl` constructor rejects with a RangeError: `_` becomes `-`,\n * then the tag is canonicalised (`fr-FR`). Falls back to `\"en\"`, with a\n * warning, only when the tag is still invalid (empty, garbage) — meant to run\n * once, where the config is read.\n */\nexport function canonicalizeLocale(locale: string): string {\n const canonical = canonicalTag(locale);\n if (canonical) return canonical;\n console.warn(`[siteping] Invalid locale \"${locale}\", falling back to \"en\"`);\n return \"en\";\n}\n\n/**\n * Build the i18n machinery for one message catalog.\n *\n * @param en — the complete English catalog, bundled synchronously as the fallback.\n * @param loaders — lazy import thunks for every other built-in locale.\n */\nexport function createI18n<T extends Record<keyof T & string, string>>(en: T, loaders: LocaleLoaders<T>): I18n<T> {\n // Built-in dictionaries and host overrides live apart, so neither erases\n // the other: an override hides only the keys it names, and a chunk that\n // lands after `registerLocale` never overwrites it. Call order is moot.\n // Prototype-free, so a code like `constructor` reads as unregistered.\n const builtins: Record<string, Partial<T>> = Object.assign(Object.create(null), { en });\n const overrides: Record<string, Partial<T>> = Object.create(null);\n\n // The loader map is typed to hold exactly the non-English built-ins, so\n // its own keys are the list — no copy of `BUILTIN_LOCALES` ships at runtime.\n function isBuiltinNonEn(lang: string): lang is Exclude<BuiltinLocale, \"en\"> {\n return Object.hasOwn(loaders, lang);\n }\n\n return {\n registerLocale(code, translations) {\n const lang = normaliseLang(code);\n overrides[lang] = { ...overrides[lang], ...translations };\n },\n\n async loadLocale(locale) {\n const lang = normaliseLang(locale);\n if (isBuiltinNonEn(lang)) builtins[lang] ??= await loaders[lang]();\n return overrides[lang] ? { ...builtins[lang], ...overrides[lang] } : (builtins[lang] ?? null);\n },\n\n createT(locale) {\n const lang = normaliseLang(locale);\n if (!builtins[lang] && !overrides[lang] && !isBuiltinNonEn(lang)) {\n console.warn(`[siteping] Unknown locale \"${locale}\", falling back to \"en\"`);\n }\n // Read the registry at call time so the returned function picks up\n // dictionaries registered/loaded after it was created.\n return (key) => overrides[lang]?.[key] ?? builtins[lang]?.[key] ?? en[key] ?? key;\n },\n };\n}\n\n/**\n * Interpolate `{paramName}` placeholders in a translated string with the\n * values from `params`. Stringifies numbers and booleans inline so callers\n * can pass `t(\"marker.count\")` along with `{ count: 3 }` directly.\n *\n * Unknown placeholders are left as-is.\n */\nexport function interpolate(template: string, params: Readonly<Record<string, string | number | boolean>>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) => {\n const value = params[name];\n return value === undefined ? match : String(value);\n });\n}\n\n/** Shorthand for `interpolate(t(key), params)` — key-checked against the catalog. */\nexport function tWithParams<K extends string>(\n t: (key: K) => string,\n key: K,\n params: Readonly<Record<string, string | number | boolean>>,\n): string {\n return interpolate(t(key), params);\n}\n","/**\n * HTTP wire helpers shared by every client that talks to a Siteping\n * endpoint (widget `ApiClient`, dashboard `createEndpointSource`).\n *\n * One definition of the query-string encoding and the HTTP→typed-error\n * mapping — two clients implementing them independently already drifted\n * once (the dashboard forgot to serialize `statuses`), so they live here\n * now.\n */\n\nimport { SitepingAuthError, SitepingError, SitepingNetworkError, SitepingValidationError } from \"./errors.js\";\nimport type { FeedbackQuery } from \"./types.js\";\n\n/**\n * A fresh `clientId` for a write the server dedupes a resend of — a feedback,\n * or a reply in a thread.\n */\nexport function newClientId(): string {\n // crypto.randomUUID() throws in non-secure contexts (plain HTTP)\n try {\n return crypto.randomUUID();\n } catch {\n return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n }\n}\n\n/**\n * Encode a `FeedbackQuery` as the endpoint's expected query string.\n * Omitted/empty filters are not serialized; `statuses` uses the CSV form\n * the server's schema splits (`statuses=open,in_progress`).\n */\nexport function feedbackQueryToSearchParams(query: FeedbackQuery): URLSearchParams {\n const params = new URLSearchParams({ projectName: query.projectName });\n if (query.page) params.set(\"page\", String(query.page));\n if (query.limit) params.set(\"limit\", String(query.limit));\n if (query.type) params.set(\"type\", query.type);\n if (query.status) params.set(\"status\", query.status);\n if (query.statuses?.length) params.set(\"statuses\", query.statuses.join(\",\"));\n if (query.search) params.set(\"search\", query.search);\n if (query.url) params.set(\"url\", query.url);\n if (query.urlPattern) params.set(\"urlPattern\", query.urlPattern);\n return params;\n}\n\n/**\n * Merge caller-supplied `extra` headers over a client's `defaults`\n * (`Content-Type`, `Authorization` from `apiKey`). Header names are\n * case-insensitive, so a default spelled differently is dropped and the\n * explicit value replaces it — a plain object merge kept both, and fetch\n * joined them (`authorization: \"Basic x\"` over an apiKey went out as\n * \"Bearer k, Basic x\").\n */\nexport function mergeRequestHeaders(\n defaults: Record<string, string>,\n extra: Record<string, string> | undefined,\n): Record<string, string> {\n const merged = { ...defaults };\n for (const [name, value] of Object.entries(extra ?? {})) {\n for (const key of Object.keys(merged)) {\n if (key.toLowerCase() === name.toLowerCase()) delete merged[key];\n }\n merged[name] = value;\n }\n return merged;\n}\n\n/**\n * Append `params` to `endpoint`, which may already carry a query string\n * (`/api/siteping?tenant=acme`) or a fragment — naive `${endpoint}?${params}`\n * produced `?tenant=acme?projectName=…`, a 400. Plain string work, so\n * relative endpoints stay relative and nothing depends on `location`.\n */\nexport function withSearchParams(endpoint: string, params: URLSearchParams): string {\n const query = params.toString();\n if (!query) return endpoint;\n const hashAt = endpoint.includes(\"#\") ? endpoint.indexOf(\"#\") : endpoint.length;\n const base = endpoint.slice(0, hashAt);\n const separator = /[?&]$/.test(base) ? \"\" : base.includes(\"?\") ? \"&\" : \"?\";\n return base + separator + query + endpoint.slice(hashAt);\n}\n\n/**\n * Map a non-OK `Response` to the appropriate typed error:\n * - 401 / 403 → `SitepingAuthError`, with its `status`\n * - 5xx / 408 / 429 → generic `SitepingError` (code `\"SERVER\"`,\n * retryable): a server failure, or a timeout or a rate limit in front of\n * the endpoint, not a verdict on the request — the same request can\n * succeed later\n * - other 4xx → `SitepingValidationError`, with its `status`\n * - anything else (a 3xx a manual redirect left unfollowed) → generic\n * `SitepingError` (code `\"SERVER\"`), not retryable\n *\n * The response body is consumed via `.text()` so the caller keeps the\n * server-supplied message in the thrown error; `.text()` failures fall back\n * to `\"Unknown error\"` (kept verbatim — host apps grep for it).\n */\nexport async function errorFromResponse(response: Response, label: string): Promise<SitepingError> {\n const { status } = response;\n const text = await response.text().catch(() => \"Unknown error\");\n const message = `${label}: ${text ? `${status} ${text}` : status}`;\n if (status === 401 || status === 403) return new SitepingAuthError(message, status);\n if (status >= 500 || status === 408 || status === 429) return new SitepingError(message, \"SERVER\", true);\n if (status >= 400) return new SitepingValidationError(message, status);\n return new SitepingError(message, \"SERVER\", false);\n}\n\n/**\n * An error's `status` (`SitepingValidationError.status`, or a custom\n * source's) or `code` (a store error's) — read as a plain field, not through\n * `instanceof`: an instance carries its code anyway, and the store error\n * classes stay out of the browser bundles. Anything thrown is safe to read:\n * a primitive has neither field, `null` and `undefined` are skipped.\n */\nconst fieldOf = (error: unknown, key: \"status\" | \"code\"): unknown =>\n (error as Partial<Record<\"status\" | \"code\", unknown>> | null | undefined)?.[key];\n\n/**\n * Whether a failed reply met a full thread — a store's `StoreLimitError`, or\n * a 409 (the endpoint's other 409, a clientId reused on another feedback,\n * never comes from clients that mint one per reply). Retrying won't help.\n */\nexport function isThreadFull(error: unknown): boolean {\n return fieldOf(error, \"status\") === 409 || fieldOf(error, \"code\") === \"STORE_LIMIT\";\n}\n\n/**\n * Whether a failed reply delete found nothing to delete — a store's\n * `StoreNotFoundError`, or a 404: it is gone already. (Unlike\n * `isStoreNotFound`, no Prisma `P2025`: a browser source never meets one.)\n */\nexport function isCommentGone(error: unknown): boolean {\n return fieldOf(error, \"status\") === 404 || fieldOf(error, \"code\") === \"STORE_NOT_FOUND\";\n}\n\n/**\n * Normalise an exception thrown by `fetch` (or a timeout AbortController)\n * into a `SitepingNetworkError`. AbortErrors count as network failures —\n * in Siteping client code they always come from internal timeouts, never a\n * user-driven cancellation.\n */\nexport function networkErrorFromException(error: unknown, label: string): SitepingNetworkError {\n if (error instanceof SitepingNetworkError) return error;\n const detail = error instanceof Error ? error.message : String(error);\n return new SitepingNetworkError(`${label}: ${detail}`);\n}\n","/** Maximum z-index value — ensures the widget renders above all page content. */\nexport const Z_INDEX_MAX = 2147483647;\n\n/**\n * Default `minViewportWidth` — 0 renders the widget at every width. Phones\n * get the compact layout (bottom sheets, touch targets) instead of being\n * skipped; hosts that still want to hide it on small screens set a threshold.\n */\nexport const DEFAULT_MIN_VIEWPORT_WIDTH = 0;\n\n/**\n * Viewports that get the compact phone layout: the panel, the feedback form\n * and the identity prompt become bottom sheets. A phone up to 640px wide, or\n * one held sideways — a short touch screen, where the on-screen keyboard\n * covers half the height. Read by `viewport.ts` and `styles/mobile.ts`.\n */\nexport const COMPACT_MEDIA = \"(max-width: 640px), (max-height: 500px) and (pointer: coarse)\";\n\n/** Default number of feedbacks to fetch per page. */\nexport const PAGE_SIZE = 20;\n\n/**\n * Size in CSS pixels of the point-rect created by the instant (right-click)\n * annotation flow. Large enough for `findAnchorElement` to resolve the target\n * but small enough to feel like a point click rather than an area selection.\n */\nexport const INSTANT_ANNOTATION_SIZE = 20;\n\n/**\n * Duration in milliseconds of the annotation popup's open/close transition.\n * The popup is set to `display: none` only once the fade-out has finished.\n */\nexport const POPUP_HIDE_TRANSITION_MS = 250;\n","/**\n * Safe DOM creation utilities.\n * All user content is set via textContent (never innerHTML).\n * SVG icons use a DOMParser for trusted static strings.\n */\n\n/**\n * Parse a trusted SVG string into an SVGElement.\n * Only use with hardcoded icon constants — never with user input.\n * Uses createContextualFragment for native document-context parsing\n * (DOMParser creates nodes in a foreign document that don't render in Shadow DOM).\n */\nexport function parseSvg(svgString: string): SVGSVGElement {\n const range = document.createRange();\n const fragment = range.createContextualFragment(svgString);\n const svg = fragment.firstElementChild;\n if (svg?.nodeName.toLowerCase() !== \"svg\") {\n throw new Error(\"[siteping] Invalid SVG string\");\n }\n // Safety: strip any event handlers in case of accidental misuse\n for (const attr of [...svg.attributes]) {\n if (attr.name.startsWith(\"on\")) svg.removeAttribute(attr.name);\n }\n // Also strip from all descendants\n for (const el of svg.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n if (attr.name.startsWith(\"on\")) el.removeAttribute(attr.name);\n }\n }\n return svg as SVGSVGElement;\n}\n\n/** Create an element with optional class and style */\nexport function el(tag: string, attrs?: Record<string, string>): HTMLElement {\n const element = document.createElement(tag);\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (key === \"class\") {\n element.className = value;\n } else if (key === \"style\") {\n element.style.cssText = value;\n } else {\n element.setAttribute(key, value);\n }\n }\n }\n return element;\n}\n\n/** Set text content safely (no HTML injection possible) */\nexport function setText(element: HTMLElement | SVGElement, text: string): void {\n element.textContent = text;\n}\n\n/**\n * Call `onOutside` for every click outside `container` until the returned\n * disposer runs. Arm it once `container` sits in its shadow tree.\n *\n * A document listener alone cannot tell: it sees a click inside the widget's\n * shadow tree retargeted to the host, and composedPath() hides a closed\n * tree's nodes from it. So the tree's own root judges clicks inside it (the\n * target is the real element there) and the document handles the rest of\n * the page.\n */\nexport function onClickOutside(container: HTMLElement, onOutside: () => void): () => void {\n const root = container.getRootNode();\n const host = root instanceof ShadowRoot ? root.host : null;\n const inTree = (e: Event) => {\n if (!container.contains(e.target as Node)) onOutside();\n };\n const onPage = (e: Event) => {\n if (e.target !== host) onOutside();\n };\n root.addEventListener(\"click\", inTree, true);\n if (host) document.addEventListener(\"click\", onPage, true);\n return () => {\n root.removeEventListener(\"click\", inTree, true);\n if (host) document.removeEventListener(\"click\", onPage, true);\n };\n}\n\n/**\n * Hide or show an element. `hidden` alone loses to a class that sets\n * `display`, and the panel's focus trap reads `style.display`.\n */\nexport function setHidden(element: HTMLElement, hidden: boolean): void {\n element.hidden = hidden;\n element.style.display = hidden ? \"none\" : \"\";\n}\n\n/**\n * Take a closed layer out of the tab order and the accessibility tree, or\n * put it back. Layers that animate out (a transform, an opacity fade) stay\n * rendered while closed, so without `inert` Tab still walks into them.\n */\nexport function setInert(element: HTMLElement, inert: boolean): void {\n element.toggleAttribute(\"inert\", inert);\n}\n\n/** Whether focus — in this element's own tree, shadow roots included — sits inside it. */\nexport function hasFocusWithin(element: HTMLElement): boolean {\n const root = element.getRootNode() as Document | ShadowRoot;\n const active = root.activeElement;\n return active !== null && element.contains(active);\n}\n\n/**\n * Replace a button's children with a small spinner and disable it.\n * Returns a `restore` callback that swaps the original content back and\n * re-enables the button. Used by every async button (delete, resolve, …)\n * to surface in-flight state without owning per-button state itself.\n *\n * Lives in dom-utils rather than panel-internal because both Panel and\n * BulkActions need identical behaviour.\n */\nexport function setButtonLoading(btn: HTMLButtonElement): () => void {\n const snapshot = Array.from(btn.childNodes).map((n) => n.cloneNode(true));\n btn.disabled = true;\n btn.replaceChildren(el(\"div\", { class: \"sp-spinner sp-spinner--sm\" }));\n return () => {\n btn.replaceChildren(...snapshot);\n btn.disabled = false;\n };\n}\n\n/**\n * Detect whether the host platform uses ⌘+Enter (macOS) vs Ctrl+Enter — the\n * send shortcut hinted by the popup and the thread's composer. Resolved at\n * call time so the popup can recompute its hint when the locale dictionary\n * lands.\n */\nexport function isMacPlatform(): boolean {\n const uaData = (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData;\n return uaData\n ? uaData.platform === \"macOS\"\n : (navigator.platform?.includes(\"Mac\") ?? /Macintosh|Mac OS X/i.test(navigator.userAgent));\n}\n\n/** Format a relative date string using Intl.RelativeTimeFormat for locale support */\nexport function formatRelativeDate(isoString: string, locale = \"en\"): string {\n const diff = Date.now() - new Date(isoString).getTime();\n const seconds = Math.floor(diff / 1000);\n\n if (seconds < 60) {\n return new Intl.RelativeTimeFormat(locale, { numeric: \"auto\" }).format(0, \"second\");\n }\n\n const rtf = new Intl.RelativeTimeFormat(locale, { numeric: \"always\", style: \"narrow\" });\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return rtf.format(-minutes, \"minute\");\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return rtf.format(-hours, \"hour\");\n\n const days = Math.floor(hours / 24);\n if (days < 7) return rtf.format(-days, \"day\");\n\n return new Date(isoString).toLocaleDateString(locale);\n}\n","/**\n * Keeps the widget usable on top of host modals (Radix / shadcn `Dialog`,\n * Headless UI, React Aria, focus-trap…) without depending on any of them.\n *\n * The widget's surfaces (the shadow host, and the overlay, toolbar, popup,\n * markers, tooltip and live region as `<body>` children) sit outside the\n * modal's subtree, so the modal reads every interaction with them as\n * \"outside\": it closes on pointer, click or focus there, pulls focus back\n * into the dialog, cancels wheel scrolling, closes on Escape, and hides its\n * siblings with `inert` / `aria-hidden`.\n *\n * - {@link isolateFromHost} stops those events as they bubble out of the\n * surface — document listeners in the bubble phase never see the widget's\n * own interactions, while the widget's listeners on the surface still run —\n * and strips a host-set `inert` / `aria-hidden` from it. Keys typed in a\n * widget field stop there too (Tab and Escape excepted): a host's\n * single-key shortcuts would otherwise fire, and swallow the character.\n * - {@link installHostIsolationGuard} covers the host listeners that run\n * before the surface: capture-phase ones on `document`, and the `focusout`\n * fired on the host element that focus leaves for the widget. It acts only\n * while a host modal is open, so a page without one keeps every event.\n * Tab is one of them: the widget's traps register through\n * {@link addSurfaceKeydownListener}, so the guard runs them first and a\n * host's capture-phase trap never sees the key.\n *\n * Known limits: a native `<dialog>` opened with `showModal()` makes the rest\n * of the page inert with no attribute to undo, and capture-phase host\n * listeners for `mousedown`, `touchstart` or `click` still see the widget's\n * events (its own listeners need them). So do capture-phase key listeners on\n * `window` or `document` for the keys typed in a widget field (the surface\n * stops those keys in the bubble phase only, which covers the usual shortcut\n * handlers), and a host's own capture-phase listeners on `window` registered\n * before the widget, which run ahead of the guard.\n */\n\n/** Events a host modal reads as an outside interaction, or cancels (wheel scroll locks). */\nconst OUTSIDE_INTERACTION_EVENTS = [\n \"pointerdown\",\n \"mousedown\",\n \"touchstart\",\n \"touchend\",\n \"click\",\n \"focusin\",\n \"focusout\",\n \"wheel\",\n \"touchmove\",\n] as const;\n\n/** What a sibling-hiding modal sets on the `<body>` children outside its dialog. */\nconst HOST_HIDING_ATTRIBUTES = [\"inert\", \"aria-hidden\"];\n\nconst surfaces = new WeakSet<Node>();\n/** Surfaces whose `inert` is the widget's own (the closing popup), left in place. */\nconst ownInert = new WeakSet<Element>();\n/** The widget's shadow root by host: at `window` a closed root hides the focused element. */\nconst shadowRoots = new WeakMap<Element, ShadowRoot>();\n/** Open-state predicates of the widget layers that close on Escape, by the node they listen on. */\nconst escapeLayers = new WeakMap<Node, Set<() => boolean>>();\n/** Widget `keydown` listeners that handle Tab, by the node they listen on. */\nconst tabListeners = new WeakMap<Node, Set<(event: KeyboardEvent) => void>>();\n\n/** Parent of `node`, crossing from a shadow root to its host. */\nconst composedParent = (node: Node): Node | null => (node instanceof ShadowRoot ? node.host : node.parentNode);\n\n/**\n * True when `node` is, or lives inside, a surface registered through\n * {@link isolateFromHost}. Narrower than `isWidgetChrome`: host elements\n * masked with `data-siteping-ignore` are not widget surfaces.\n */\nexport function isWidgetSurface(node: Node): boolean {\n for (let current: Node | null = node; current; current = composedParent(current)) {\n if (surfaces.has(current)) return true;\n }\n return false;\n}\n\nconst isSurfaceTarget = (target: EventTarget | null): boolean => target instanceof Node && isWidgetSurface(target);\n\nconst stopAtSurface = (event: Event): void => event.stopPropagation();\n\n/**\n * Keep a key typed in a widget field from the page. Host shortcuts (`/` for\n * search, `j`/`k`, `s`…) skip text fields by their target, but an event from\n * a closed shadow tree reaches the page retargeted to its host, a plain\n * element: the shortcut fires and its `preventDefault()` drops the character.\n * Tab and Escape pass — focus traps and Escape layers read them.\n */\nconst keepTypingInWidget = (event: Event): void => {\n const { key } = event as KeyboardEvent;\n const field = focusedTarget(event as KeyboardEvent);\n if (\n key !== \"Tab\" &&\n key !== \"Escape\" &&\n field instanceof HTMLElement &&\n (field.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(field.tagName))\n ) {\n event.stopPropagation();\n }\n};\n\n/**\n * Register `surface` as widget UI: host document listeners stop seeing its\n * outside-interaction events, and host-set `inert` / `aria-hidden` on it is\n * removed now and whenever it comes back. Pass the shadow root when\n * `surface` hosts a closed one.\n */\nexport function isolateFromHost(surface: HTMLElement, shadowRoot?: ShadowRoot): void {\n surfaces.add(surface);\n if (shadowRoot) shadowRoots.set(surface, shadowRoot);\n for (const type of OUTSIDE_INTERACTION_EVENTS) {\n surface.addEventListener(type, stopAtSurface, { passive: true });\n }\n for (const type of [\"keydown\", \"keypress\", \"keyup\"]) surface.addEventListener(type, keepTypingInWidget);\n const unhide = (): void => {\n for (const name of HOST_HIDING_ATTRIBUTES) {\n if (name !== \"inert\" || !ownInert.has(surface)) surface.removeAttribute(name);\n }\n };\n unhide();\n // Collected with the surface, which holds the only reference to it.\n new MutationObserver(unhide).observe(surface, { attributeFilter: HOST_HIDING_ATTRIBUTES });\n}\n\n/** Set or clear a surface's own `inert`, which {@link isolateFromHost} then leaves alone. */\nexport function setSurfaceInert(surface: HTMLElement, inert: boolean): void {\n if (inert) ownInert.add(surface);\n else ownInert.delete(surface);\n surface.toggleAttribute(\"inert\", inert);\n}\n\n/**\n * Declare a widget layer that closes on Escape, so the guard marks that\n * Escape as handled for host modals only while `isOpen()` says the layer will\n * consume it. `scope` is the node its Escape handler listens on — an element,\n * the shadow root, or `document` for session-wide handlers.\n *\n * @returns Cleanup unregistering the layer.\n */\nexport function registerEscapeLayer(scope: Node, isOpen: () => boolean): () => void {\n let layers = escapeLayers.get(scope);\n if (!layers) {\n layers = new Set();\n escapeLayers.set(scope, layers);\n }\n layers.add(isOpen);\n return () => {\n layers.delete(isOpen);\n };\n}\n\n/**\n * Listen for `keydown` on a widget node — an element of a surface, or the\n * shadow root — with a listener that handles Tab (a focus trap). It runs like\n * any listener; and while a host modal is open, the guard runs it for a Tab\n * from `window` in the capture phase, then hides that Tab from the page: a\n * host trap listening on `document` in the capture phase (focus-trap,\n * Headless UI) would otherwise move focus back into its dialog first.\n * `check:consistency` rejects a Tab-handling listener added any other way.\n *\n * @returns Cleanup removing the listener.\n */\nexport function addSurfaceKeydownListener(target: Node, listener: (event: KeyboardEvent) => void): () => void {\n let listeners = tabListeners.get(target);\n if (!listeners) {\n listeners = new Set();\n tabListeners.set(target, listeners);\n }\n listeners.add(listener);\n target.addEventListener(\"keydown\", listener as EventListener);\n return () => {\n listeners.delete(listener);\n target.removeEventListener(\"keydown\", listener as EventListener);\n };\n}\n\n/**\n * A host modal is open: an `aria-modal=\"true\"` element is rendered and visible\n * outside the widget, or `<body>` is click-through (how Radix / shadcn modals\n * disable the rest of the page). A `visibility: hidden` element still has\n * boxes, hence the separate check for closed dialogs kept mounted that way.\n */\nfunction isHostModalOpen(): boolean {\n if (getComputedStyle(document.body).pointerEvents === \"none\") return true;\n for (const dialog of document.querySelectorAll('[aria-modal=\"true\"]')) {\n if (\n dialog.getClientRects().length > 0 &&\n getComputedStyle(dialog).visibility === \"visible\" &&\n !isWidgetSurface(dialog)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * The element a keyboard event comes from. At `window` a closed shadow tree\n * is retargeted to its host, so descend through the focused element of each\n * shadow root (open, or registered through {@link isolateFromHost}).\n */\nfunction focusedTarget(event: KeyboardEvent): Node | null {\n let target = event.composedPath()[0];\n while (target instanceof Element) {\n const focused = (target.shadowRoot ?? shadowRoots.get(target))?.activeElement;\n if (!focused) break;\n target = focused;\n }\n return target instanceof Node ? target : null;\n}\n\n/** Whether an open layer on the Escape's path (up to `document`) will consume it. */\nfunction willWidgetConsumeEscape(event: KeyboardEvent): boolean {\n for (let node = focusedTarget(event); node; node = composedParent(node)) {\n for (const isOpen of escapeLayers.get(node) ?? []) {\n if (isOpen()) return true;\n }\n }\n return false;\n}\n\n/**\n * Hide the widget from a host modal's listeners that run before its surfaces,\n * from `window` in the capture phase — ahead of any listener on `document`.\n * Each check runs at event time and only while a host modal is open:\n *\n * - `pointerdown` / `focusin` on a surface are stopped (no widget listener\n * consumes them): dismiss-on-outside and focus-trap listeners skip them.\n * - `focusout` is stopped when focus leaves a surface or moves from the page\n * into one, so a focus trap does not pull it back into the dialog. The\n * page element being left misses its own `focusout` (React `onBlur`) for\n * that one move — never while no modal is open.\n * - An Escape from a surface is marked handled (`preventDefault()`) when an\n * open widget layer will consume it ({@link registerEscapeLayer}): the\n * widget still closes that layer, and modals that respect\n * `defaultPrevented` (Radix, Headless UI) stay open. With nothing open in\n * the widget, Escape still closes the modal.\n *\n * @returns Cleanup removing the guard.\n */\nexport function installHostIsolationGuard(): () => void {\n const hideFromHost = (event: Event): void => {\n if (isSurfaceTarget(event.target) && isHostModalOpen()) event.stopImmediatePropagation();\n };\n const onFocusOut = (event: FocusEvent): void => {\n if ((isSurfaceTarget(event.target) || isSurfaceTarget(event.relatedTarget)) && isHostModalOpen()) {\n event.stopImmediatePropagation();\n }\n };\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key !== \"Tab\" && event.key !== \"Escape\") return;\n if (!isSurfaceTarget(event.target) || !isHostModalOpen()) return;\n if (event.key === \"Escape\") {\n if (willWidgetConsumeEscape(event)) event.preventDefault();\n return;\n }\n // Tab: run the widget's traps now, innermost first as the event would\n // reach them, then stop it — the browser still moves focus by default\n // when no trap prevented it, but no page listener sees the key.\n for (let node = focusedTarget(event); node; node = composedParent(node)) {\n for (const listener of [...(tabListeners.get(node) ?? [])]) listener(event);\n }\n event.stopImmediatePropagation();\n };\n const listeners = [\n [\"pointerdown\", hideFromHost],\n [\"focusin\", hideFromHost],\n [\"focusout\", onFocusOut],\n [\"keydown\", onKeyDown],\n ] as const;\n for (const [type, listener] of listeners) window.addEventListener(type, listener as EventListener, true);\n return () => {\n for (const [type, listener] of listeners) window.removeEventListener(type, listener as EventListener, true);\n };\n}\n","/** SVG icon strings for the widget UI. Kept as template strings to avoid DOM parsing overhead. */\n\nexport const ICON_SITEPING = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><circle cx=\"12\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"8\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"16\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/></svg>`;\n\nexport const ICON_EYE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>`;\n\nexport const ICON_EYE_OFF = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94\"/><path d=\"M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19\"/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/></svg>`;\n\nexport const ICON_CLOSE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`;\n\nexport const ICON_SEARCH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>`;\n\nexport const ICON_CHECK = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\n\n/** The closed bucket (resolved + wont_fix): neutral, since a check means resolved. */\nexport const ICON_ARCHIVE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"3\" width=\"20\" height=\"5\" rx=\"1\"/><path d=\"M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8M10 12h4\"/></svg>`;\n\nexport const ICON_QUESTION = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/></svg>`;\n\nexport const ICON_CHANGE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/></svg>`;\n\nexport const ICON_BUG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"8\" y=\"6\" width=\"8\" height=\"14\" rx=\"4\"/><path d=\"M19 9h2\"/><path d=\"M3 9h2\"/><path d=\"M19 13h2\"/><path d=\"M3 13h2\"/><path d=\"M19 17h2\"/><path d=\"M3 17h2\"/><path d=\"M10 2h4\"/></svg>`;\n\nexport const ICON_OTHER = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/></svg>`;\n\nexport const ICON_LAYERS = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polygon points=\"12 2 2 7 12 12 22 7 12 2\"/><polyline points=\"2 17 12 22 22 17\"/><polyline points=\"2 12 12 17 22 12\"/></svg>`;\n\nexport const ICON_DOT_OPEN = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"12\" cy=\"12\" r=\"3\" fill=\"currentColor\" stroke=\"none\"/></svg>`;\n\nexport const ICON_CHEVRON_DOWN = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\"/></svg>`;\n\nexport const ICON_UNDO = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>`;\n\nexport const ICON_TRASH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/></svg>`;\n\n// Person (Feather \"user\"). Used for the panel's \"Mine\" filter and the detail view's author row.\nexport const ICON_USER = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>`;\n\n// List icon (Feather \"list\") — bullet rows. Used for the FAB radial item that opens the feedback sidebar.\nexport const ICON_LIST = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>`;\n\n// Pencil / edit-3 (Feather \"edit-3\") — pure pencil with no document. Used for the FAB radial item that creates a new annotation.\nexport const ICON_EDIT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z\"/></svg>`;\n","/** Color palette and glassmorphism tokens derived from the accent color */\nexport interface ThemeColors {\n accent: string;\n accentLight: string;\n accentDark: string;\n accentGlow: string;\n accentGradient: string;\n /** Accent for text: reads at 4.5:1 on the accent's own tinted background (`accentLight`). */\n accentText: string;\n bg: string;\n bgHover: string;\n text: string;\n textSecondary: string;\n textTertiary: string;\n border: string;\n shadow: string;\n // Glass tokens\n glassBg: string;\n glassBgHeavy: string;\n glassBorder: string;\n glassBorderSubtle: string;\n // Feedback type colors\n typeQuestion: string;\n typeChange: string;\n typeBug: string;\n typeOther: string;\n // Soft type backgrounds (pastel)\n typeQuestionBg: string;\n typeChangeBg: string;\n typeBugBg: string;\n typeOtherBg: string;\n // Status filter colors\n statusOpen: string;\n statusOpenBg: string;\n statusResolved: string;\n statusResolvedBg: string;\n statusInProgress: string;\n statusInProgressBg: string;\n statusWontFix: string;\n statusWontFixBg: string;\n}\n\nconst DEFAULT_ACCENT = \"#0066ff\";\nconst HEX6_RE = /^#[0-9a-fA-F]{6}$/;\nconst HEX3_RE = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/;\nconst HEX8_RE = /^#[0-9a-fA-F]{8}$/;\n\n/**\n * Normalize an accent color to a 6-digit hex string.\n *\n * **Only hex formats are accepted:**\n * - `#RGB` (3-digit shorthand, expanded to 6-digit)\n * - `#RRGGBB` (standard 6-digit)\n * - `#RRGGBBAA` (8-digit with alpha, alpha is stripped)\n *\n * Any other CSS color format (named colors like `\"red\"`, `hsl()`, `rgb()`,\n * `oklch()`, etc.) is **not** supported and will fall back to the default\n * accent color with a console warning.\n */\nfunction normalizeHex(raw: string): string {\n if (HEX6_RE.test(raw)) return raw;\n const short = HEX3_RE.test(raw) ? raw.match(HEX3_RE) : null;\n if (short) return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;\n if (HEX8_RE.test(raw)) return raw.slice(0, 7);\n\n console.warn(\n `[siteping] Invalid accentColor \"${raw}\" — only hex colors (#RGB, #RRGGBB, #RRGGBBAA) are supported. Using default.`,\n );\n return DEFAULT_ACCENT;\n}\n\n/** `hex` mixed toward `toward` by `amount` (0-1), both 6-digit hex. */\nfunction mixHex(hex: string, toward: string, amount: number): string {\n let out = \"#\";\n for (let i = 1; i < 7; i += 2) {\n const from = parseInt(hex.slice(i, i + 2), 16);\n const channel = Math.round(from + (parseInt(toward.slice(i, i + 2), 16) - from) * amount);\n out += channel.toString(16).padStart(2, \"0\");\n }\n return out;\n}\n\n/** WCAG 2.1 relative luminance of a 6-digit hex color. */\nfunction luminance(hex: string): number {\n let sum = 0;\n for (const [i, weight] of [\n [1, 0.2126],\n [3, 0.7152],\n [5, 0.0722],\n ] as const) {\n const c = parseInt(hex.slice(i, i + 2), 16) / 255;\n sum += weight * (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);\n }\n return sum;\n}\n\n/**\n * `hex` moved toward `toward` (black or white) in 10% steps until it reads at\n * 4.5:1 on `bg` — AA for small text. The accent is the host's to pick, so\n * text drawn in it is derived from it rather than trusted.\n */\nfunction readableOn(hex: string, bg: string, toward: string): string {\n const background = luminance(bg);\n let color = hex;\n for (let step = 1; step <= 10; step++) {\n const text = luminance(color);\n if ((Math.max(text, background) + 0.05) / (Math.min(text, background) + 0.05) >= 4.5) break;\n color = mixHex(hex, toward, step / 10);\n }\n return color;\n}\n\nconst DARK_QUERY = \"(prefers-color-scheme: dark)\";\n\n/** Detect if user prefers dark mode via media query */\nfunction prefersDark(): boolean {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return false;\n return window.matchMedia(DARK_QUERY).matches;\n}\n\n/** Call `onChange` whenever the OS color scheme flips; returns the unsubscribe. */\nexport function watchColorScheme(onChange: () => void): () => void {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return () => {};\n const query = window.matchMedia(DARK_QUERY);\n query.addEventListener(\"change\", onChange);\n return () => query.removeEventListener(\"change\", onChange);\n}\n\n/** Resolve 'auto' theme to 'light' or 'dark' based on system preference */\nexport function resolveTheme(theme?: \"light\" | \"dark\" | \"auto\"): \"light\" | \"dark\" {\n if (theme === \"dark\") return \"dark\";\n if (theme === \"auto\") return prefersDark() ? \"dark\" : \"light\";\n return \"light\";\n}\n\nexport function buildThemeColors(accent: string = DEFAULT_ACCENT, theme?: \"light\" | \"dark\" | \"auto\"): ThemeColors {\n const hex = normalizeHex(accent);\n const dark = mixHex(hex, \"#000000\", 0.15);\n const resolved = resolveTheme(theme);\n\n if (resolved === \"dark\") {\n return {\n accent: hex,\n accentLight: hex + \"22\", // slightly more visible on dark bg\n accentDark: dark,\n accentGlow: hex + \"44\",\n accentGradient: `linear-gradient(135deg, ${hex}, ${dark})`,\n // Lightened until it reads on accentLight over bgHover, the lighter of the two grounds.\n accentText: readableOn(hex, mixHex(\"#1e293b\", hex, 0x22 / 255), \"#ffffff\"),\n bg: \"#0f172a\",\n bgHover: \"#1e293b\",\n text: \"#f1f5f9\",\n textSecondary: \"#94a3b8\",\n // 5.9:1 on bg, 4.8:1 on bgHover (AA for small text; slate-500 was 3.1–3.8:1).\n textTertiary: \"#8695aa\",\n border: \"#334155\",\n shadow: \"rgba(0, 0, 0, 0.3)\",\n // Glass tokens — dark frosted glass. Opaque enough that a white page\n // behind it, the worst case, leaves tertiary text at 5:1 (0.78 left 2.9:1).\n glassBg: \"rgba(15, 23, 42, 0.94)\",\n glassBgHeavy: \"rgba(15, 23, 42, 0.96)\",\n glassBorder: \"rgba(51, 65, 85, 0.5)\",\n glassBorderSubtle: \"rgba(51, 65, 85, 0.3)\",\n // Type colors stay vibrant on dark\n typeQuestion: \"#60a5fa\",\n typeChange: \"#fbbf24\",\n typeBug: \"#f87171\",\n typeOther: \"#94a3b8\",\n // Dark pastel backgrounds\n typeQuestionBg: \"rgba(59, 130, 246, 0.15)\",\n typeChangeBg: \"rgba(245, 158, 11, 0.15)\",\n typeBugBg: \"rgba(239, 68, 68, 0.15)\",\n typeOtherBg: \"rgba(100, 116, 139, 0.15)\",\n // Status colors — vivid green / cool gray on dark\n statusOpen: \"#4ade80\",\n statusOpenBg: \"rgba(74, 222, 128, 0.15)\",\n // Slate lifted from #94a3b8, which read 4.4:1 on its tint over bgHover.\n statusResolved: \"#a3b0c2\",\n statusResolvedBg: \"rgba(148, 163, 184, 0.15)\",\n statusInProgress: \"#fbbf24\",\n statusInProgressBg: \"rgba(245, 158, 11, 0.15)\",\n statusWontFix: \"#a3b0c2\",\n statusWontFixBg: \"rgba(148, 163, 184, 0.15)\",\n };\n }\n\n return {\n accent: hex,\n accentLight: hex + \"14\", // 8% opacity\n accentDark: dark,\n accentGlow: hex + \"33\", // 20% opacity\n accentGradient: `linear-gradient(135deg, ${hex}, ${dark})`,\n // Darkened until it reads on accentLight over bgHover, the darker of the two grounds.\n accentText: readableOn(hex, mixHex(\"#f8f9fb\", hex, 0x14 / 255), \"#000000\"),\n bg: \"#ffffff\",\n bgHover: \"#f8f9fb\",\n text: \"#0f172a\",\n textSecondary: \"#475569\",\n textTertiary: \"#64748b\",\n border: \"#e2e8f0\",\n shadow: \"rgba(0, 0, 0, 0.06)\",\n // Glass tokens\n glassBg: \"rgba(255, 255, 255, 0.72)\",\n glassBgHeavy: \"rgba(255, 255, 255, 0.85)\",\n glassBorder: \"rgba(255, 255, 255, 0.35)\",\n glassBorderSubtle: \"rgba(255, 255, 255, 0.18)\",\n // Type colors, dark enough to read as badge text on their pastel (4.5:1+);\n // blue-500, red-500 and slate-500 read 3.4–4.5:1 there.\n typeQuestion: \"#2563eb\",\n typeChange: \"#b45309\",\n typeBug: \"#d01f1f\",\n typeOther: \"#5b6778\",\n // Pastel backgrounds\n typeQuestionBg: \"#eff6ff\",\n typeChangeBg: \"#fffbeb\",\n typeBugBg: \"#fef2f2\",\n typeOtherBg: \"#f8fafc\",\n // Status colors, same rule: green-700, amber-700 and a deeper slate (green-600\n // and amber-600 read 3.1:1 on their pastel).\n statusOpen: \"#15803d\",\n statusOpenBg: \"#f0fdf4\",\n statusResolved: \"#5b6778\",\n statusResolvedBg: \"#f1f5f9\",\n statusInProgress: \"#b45309\",\n statusInProgressBg: \"#fffbeb\",\n statusWontFix: \"#5b6778\",\n statusWontFixBg: \"#f1f5f9\",\n };\n}\n\nexport function getTypeColor(type: string, colors: ThemeColors): string {\n switch (type) {\n case \"question\":\n return colors.typeQuestion;\n case \"change\":\n return colors.typeChange;\n case \"bug\":\n return colors.typeBug;\n default:\n return colors.typeOther;\n }\n}\n\nexport function getStatusColor(status: string, colors: ThemeColors): string {\n switch (status) {\n case \"in_progress\":\n return colors.statusInProgress;\n case \"resolved\":\n return colors.statusResolved;\n case \"wont_fix\":\n return colors.statusWontFix;\n default:\n return colors.statusOpen;\n }\n}\n\nexport function getStatusBgColor(status: string, colors: ThemeColors): string {\n switch (status) {\n case \"in_progress\":\n return colors.statusInProgressBg;\n case \"resolved\":\n return colors.statusResolvedBg;\n case \"wont_fix\":\n return colors.statusWontFixBg;\n default:\n return colors.statusOpenBg;\n }\n}\n\nexport function getTypeBgColor(type: string, colors: ThemeColors): string {\n switch (type) {\n case \"question\":\n return colors.typeQuestionBg;\n case \"change\":\n return colors.typeChangeBg;\n case \"bug\":\n return colors.typeBugBg;\n default:\n return colors.typeOtherBg;\n }\n}\n\nexport function cssVariables(colors: ThemeColors): string {\n return /* css */ `--sp-accent: ${colors.accent};--sp-accent-light: ${colors.accentLight};--sp-accent-dark: ${colors.accentDark};--sp-accent-glow: ${colors.accentGlow};--sp-accent-gradient: ${colors.accentGradient};--sp-accent-text: ${colors.accentText};--sp-bg: ${colors.bg};--sp-bg-hover: ${colors.bgHover};--sp-text: ${colors.text};--sp-text-secondary: ${colors.textSecondary};--sp-text-tertiary: ${colors.textTertiary};--sp-border: ${colors.border};--sp-shadow: ${colors.shadow};--sp-glass-bg: ${colors.glassBg};--sp-glass-bg-heavy: ${colors.glassBgHeavy};--sp-glass-border: ${colors.glassBorder};--sp-glass-border-subtle: ${colors.glassBorderSubtle};--sp-type-question: ${colors.typeQuestion};--sp-type-change: ${colors.typeChange};--sp-type-bug: ${colors.typeBug};--sp-type-other: ${colors.typeOther};--sp-type-question-bg: ${colors.typeQuestionBg};--sp-type-change-bg: ${colors.typeChangeBg};--sp-type-bug-bg: ${colors.typeBugBg};--sp-type-other-bg: ${colors.typeOtherBg};--sp-status-open: ${colors.statusOpen};--sp-status-open-bg: ${colors.statusOpenBg};--sp-status-resolved: ${colors.statusResolved};--sp-status-resolved-bg: ${colors.statusResolvedBg};--sp-radius: 12px;--sp-radius-lg: 16px;--sp-radius-xl: 20px;--sp-radius-full: 9999px;--sp-blur: 20px;--sp-blur-heavy: 32px;--sp-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);--sp-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.04);--sp-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);--sp-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.04);--sp-shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.06);--sp-font: \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n;\n}\n","import { COMPACT_MEDIA } from \"./constants.js\";\n\nfunction matches(query: string): boolean {\n return typeof window.matchMedia === \"function\" && window.matchMedia(query).matches;\n}\n\n/**\n * Phone-sized viewport, upright or sideways (`COMPACT_MEDIA`) — the widget\n * swaps floating surfaces for bottom sheets.\n * Elements inside the Shadow DOM use the equivalent `@media` block; this is\n * for the ones that live on `document.body` with inline styles.\n */\nexport function isCompactViewport(): boolean {\n return matches(COMPACT_MEDIA);\n}\n\n/** Primary input is a finger: no hover, and usually no hardware keyboard. */\nexport function isCoarsePointer(): boolean {\n return matches(\"(pointer: coarse)\");\n}\n\n/** Read live, not cached, so an OS setting change applies to the next animation. */\nexport function prefersReducedMotion(): boolean {\n return matches(\"(prefers-reduced-motion: reduce)\");\n}\n\n/**\n * Report how much of the layout viewport the on-screen keyboard covers.\n *\n * Opening the keyboard shrinks only the *visual* viewport (always on iOS,\n * by default on Chrome Android), so a `position: fixed; bottom: 0` sheet ends\n * up behind it. Callers lift the sheet by `inset` and cap its height to\n * `visibleHeight`. Calls `onChange` once immediately; returns an unsubscribe.\n */\nexport function trackKeyboardInset(onChange: (inset: number, visibleHeight: number) => void): () => void {\n const vv = window.visualViewport;\n if (!vv) return () => {};\n const update = () => onChange(Math.max(0, Math.round(window.innerHeight - vv.height - vv.offsetTop)), vv.height);\n update();\n vv.addEventListener(\"resize\", update);\n vv.addEventListener(\"scroll\", update);\n return () => {\n vv.removeEventListener(\"resize\", update);\n vv.removeEventListener(\"scroll\", update);\n };\n}\n","import type { Translations } from \"./types.js\";\n\nexport const en: Translations = {\n // Panel\n \"panel.title\": \"Feedbacks\",\n \"panel.ariaLabel\": \"Siteping feedback panel\",\n \"panel.feedbackList\": \"Feedback list\",\n \"panel.loading\": \"Loading feedbacks\",\n \"panel.close\": \"Close panel\",\n \"panel.deleteAll\": \"Delete all\",\n \"panel.deleteAllConfirmTitle\": \"Delete all\",\n \"panel.deleteAllConfirmMessage\": \"Delete all feedbacks for this project? This action cannot be undone.\",\n \"panel.search\": \"Search...\",\n \"panel.searchAria\": \"Search feedbacks\",\n \"panel.filterAll\": \"All\",\n \"panel.loadError\": \"Failed to load\",\n \"panel.retry\": \"Retry\",\n \"panel.empty\": \"No feedback yet\",\n \"panel.showMore\": \"Show more\",\n \"panel.notOnPage\": \"Not visible on this page\",\n \"panel.showLess\": \"Show less\",\n \"panel.resolve\": \"Resolve\",\n \"panel.reopen\": \"Reopen\",\n \"panel.delete\": \"Delete\",\n \"panel.cancel\": \"Cancel\",\n \"panel.confirmDelete\": \"Delete\",\n \"panel.loadMore\": \"Load more ({remaining} remaining)\",\n\n // Status filter labels\n \"panel.statusAll\": \"All\",\n \"panel.statusOpen\": \"Open\",\n \"panel.statusResolved\": \"Resolved\",\n \"panel.statusInProgress\": \"In progress\",\n \"panel.statusWontFix\": \"Won't fix\",\n \"panel.statusClosed\": \"Closed\",\n\n // Feedback type labels\n \"type.label\": \"Type\",\n \"type.question\": \"Question\",\n \"type.change\": \"Change\",\n \"type.bug\": \"Bug\",\n \"type.other\": \"Other\",\n\n // Status segmented control label\n \"status.label\": \"Status\",\n\n // Page scope segmented control\n \"scope.label\": \"Scope\",\n \"scope.thisPage\": \"This page\",\n \"scope.thisType\": \"This type\",\n \"scope.all\": \"All pages\",\n\n // \"Mine\" toggle\n \"panel.filterMine\": \"Mine\",\n \"panel.filterMineHint\": \"Only the feedback sent from this browser\",\n\n // FAB menu\n \"fab.aria\": \"Siteping \\u2014 Feedback menu\",\n \"fab.messages\": \"Show sidebar\",\n \"fab.annotate\": \"Create new annotation\",\n \"fab.annotations\": \"Show or hide markers\",\n\n // Annotator\n \"annotator.instruction\":\n \"Draw a rectangle on the area to comment — or press Enter to comment on the last focused element\",\n \"annotator.instantInstruction\": \"Comment on the clicked spot\",\n \"annotator.touchInstruction\": \"Tap an element or drag to select an area\",\n \"annotator.cancel\": \"Cancel\",\n\n // Popup\n \"popup.ariaLabel\": \"Feedback form\",\n \"popup.placeholder\": \"Describe your feedback...\",\n \"popup.textareaAria\": \"Feedback message\",\n \"popup.submitHintMac\": \"\\u2318+Enter to send\",\n \"popup.submitHintOther\": \"Ctrl+Enter to send\",\n \"popup.cancel\": \"Cancel\",\n \"popup.submit\": \"Send\",\n\n // Identity modal\n \"identity.title\": \"Identify yourself\",\n \"identity.nameLabel\": \"Name\",\n \"identity.namePlaceholder\": \"Your name\",\n \"identity.emailLabel\": \"Email\",\n \"identity.emailPlaceholder\": \"your@email.com\",\n \"identity.cancel\": \"Cancel\",\n \"identity.submit\": \"Continue\",\n\n // Markers\n \"marker.approximate\": \"Approximate position (confidence: {confidence}%)\",\n \"marker.aria\": \"Feedback #{number}: {type}, {status} — {message}\",\n \"marker.count\": \"{count} feedback markers displayed\",\n\n // FAB badge\n \"fab.badge\": \"{count} unresolved feedbacks\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback sent successfully\",\n \"feedback.error.message\": \"Failed to send feedback\",\n \"feedback.queued\": \"Saved on this device. It will be sent on your next visit.\",\n \"feedback.deleted.confirmation\": \"Feedback deleted\",\n\n // Badge\n \"badge.count\": \"{count} unresolved feedbacks\",\n\n // Bulk actions toolbar\n \"bulk.aria\": \"Bulk actions\",\n \"bulk.selectAll\": \"Select all\",\n \"bulk.selectFeedback\": \"Select {label}\",\n \"bulk.selected\": \"{count} selected\",\n \"bulk.resolve\": \"Resolve\",\n \"bulk.delete\": \"Delete\",\n \"bulk.deselect\": \"Deselect\",\n\n // Sort and group controls\n \"sort.newest\": \"Newest first\",\n \"sort.oldest\": \"Oldest first\",\n \"sort.byType\": \"By type\",\n \"sort.openFirst\": \"Open first\",\n \"sort.label\": \"Sort\",\n \"group.byPage\": \"By page\",\n \"group.feedbacks\": \"{count} feedbacks\",\n\n // Stats bar\n \"stats.aria\": \"Feedback statistics\",\n \"stats.open\": \"Open\",\n \"stats.closed\": \"Closed\",\n \"stats.bugs\": \"Bugs\",\n \"stats.progress\": \"{percent}% closed\",\n\n // Detail view\n \"detail.back\": \"Back\",\n \"detail.aria\": \"Feedback detail\",\n \"detail.title\": \"Feedback #{number}\",\n \"detail.status\": \"Status\",\n \"detail.message\": \"Message\",\n \"detail.screenshot\": \"Screenshot\",\n \"detail.screenshotAlt\": \"Screenshot of the annotated area\",\n \"detail.metadata\": \"Details\",\n \"detail.annotation\": \"Annotation\",\n \"detail.page\": \"Page\",\n \"detail.author\": \"Author\",\n \"detail.date\": \"Created\",\n \"detail.viewport\": \"Viewport\",\n \"detail.browser\": \"Browser\",\n \"detail.resolvedAt\": \"Resolved at\",\n \"detail.closedAt\": \"Closed at\",\n \"detail.goToAnnotation\": \"Go to annotation\",\n \"detail.element\": \"Element\",\n \"detail.selector\": \"Selector\",\n \"detail.position\": \"Position\",\n \"detail.resolve\": \"Resolve\",\n \"detail.reopen\": \"Reopen\",\n \"detail.delete\": \"Delete\",\n \"detail.diagnostics\": \"Diagnostics\",\n \"detail.diagnostics.console\": \"Console\",\n \"detail.diagnostics.network\": \"Failed network\",\n \"detail.diagnostics.expand\": \"Show diagnostics\",\n \"detail.diagnostics.collapse\": \"Hide diagnostics\",\n \"detail.diagnostics.noEntries\": \"No entries\",\n\n // Discussion thread (detail view)\n \"comments.title\": \"Replies\",\n \"comments.placeholder\": \"Reply to the team...\",\n \"comments.error\": \"Not sent. Try again.\",\n \"comments.full\": \"This thread is full.\",\n \"comments.team\": \"Team\",\n\n // Keyboard shortcuts overlay\n \"shortcuts.title\": \"Keyboard shortcuts\",\n \"shortcuts.navigate\": \"Navigate feedbacks\",\n \"shortcuts.resolve\": \"Resolve / Reopen\",\n \"shortcuts.delete\": \"Delete\",\n \"shortcuts.search\": \"Focus search\",\n \"shortcuts.select\": \"Toggle selection\",\n \"shortcuts.help\": \"Show shortcuts\",\n \"shortcuts.close\": \"Close\",\n \"shortcuts.hint\": \"Keyboard shortcuts\",\n\n // Export controls\n \"export.label\": \"Export\",\n \"export.csv\": \"Export CSV\",\n \"export.json\": \"Export JSON\",\n};\n","import type { FeedbackStatus, FeedbackType } from \"@siteping/core\";\nimport { tWithParams as coreTWithParams, createI18n, interpolate } from \"@siteping/core\";\nimport type { TFunction, TranslationKey, Translations } from \"./types.js\";\n\nexport type { TFunction, TranslationKey, Translations } from \"./types.js\";\nexport { interpolate };\n\n// `en` is bundled synchronously as the immediate fallback — every other\n// locale is a static import() thunk, so the bundler emits one chunk per\n// locale and only the resolved one ships over the network when\n// `loadLocale()` is called.\n//\n// The loader map is typed against core's BUILTIN_LOCALES: adding a locale\n// code there is a compile error here until the dictionary + loader exist.\nimport { en } from \"./en.js\";\n\nconst i18n = createI18n<Translations>(en, {\n de: () => import(\"./de.js\").then((m) => m.de),\n es: () => import(\"./es.js\").then((m) => m.es),\n fr: () => import(\"./fr.js\").then((m) => m.fr),\n it: () => import(\"./it.js\").then((m) => m.it),\n ja: () => import(\"./ja.js\").then((m) => m.ja),\n ko: () => import(\"./ko.js\").then((m) => m.ko),\n pt: () => import(\"./pt.js\").then((m) => m.pt),\n ru: () => import(\"./ru.js\").then((m) => m.ru),\n});\n\n/**\n * Register translations at runtime, for a new locale or over a built-in one —\n * call it before `initSiteping`. Partial dictionaries are welcome: under a\n * built-in code (`registerLocale(\"fr\", { \"fab.annotate\": \"Commenter\" })`) the\n * keys given override the built-in dictionary and every other key keeps its\n * built-in value; elsewhere missing keys fall back to English per key. So\n * overriding a single string never requires copying the whole catalog.\n * Calls for the same code accumulate.\n */\nexport const registerLocale: (code: string, translations: Partial<Translations>) => void = i18n.registerLocale;\n\n/**\n * Dynamically import a built-in locale (once) and register it. Returns the\n * locale as the widget resolves it — the built-in dictionary with any\n * {@link registerLocale} overrides on top — or `null` for a code that is\n * neither built in nor registered. A custom code never hits a loader.\n */\nexport const loadLocale: (locale: string) => Promise<Partial<Translations> | null> = i18n.loadLocale;\n\n/**\n * Create a translation function for the given locale.\n *\n * Locale resolution, per key: {@link registerLocale} override > built-in\n * dictionary of the language (`fr-CA` reads `fr`) > English fallback.\n * Non-English built-in locales are lazy-loaded via {@link loadLocale} — call\n * `await loadLocale(locale)` at init if you want the panel to render in the\n * target language immediately. Otherwise the widget renders in English until\n * the dictionary lands, then `createT` returns the resolved dictionary.\n */\nexport const createT: (locale: string) => TFunction = i18n.createT;\n\n/** Shorthand for `interpolate(t(key), params)`. Typed against `TranslationKey`. */\nexport const tWithParams: (\n t: TFunction,\n key: TranslationKey,\n params: Readonly<Record<string, string | number | boolean>>,\n) => string = coreTWithParams;\n\n/**\n * Returns the type label for a `FeedbackType` value.\n *\n * Maps API enum values (English) to localised display labels. Unknown\n * values fall through and render raw instead of crashing.\n */\nexport function getTypeLabel(type: FeedbackType | string, t: TFunction): string {\n switch (type) {\n case \"question\":\n return t(\"type.question\");\n case \"change\":\n return t(\"type.change\");\n case \"bug\":\n return t(\"type.bug\");\n case \"other\":\n return t(\"type.other\");\n default:\n return type;\n }\n}\n\n/**\n * Returns the status label for a `FeedbackStatus` value.\n *\n * Maps API enum values (English) to localised display labels. Unknown\n * values fall through and render raw instead of crashing.\n */\nexport function getStatusLabel(status: FeedbackStatus | string, t: TFunction): string {\n switch (status) {\n case \"open\":\n return t(\"panel.statusOpen\");\n case \"in_progress\":\n return t(\"panel.statusInProgress\");\n case \"resolved\":\n return t(\"panel.statusResolved\");\n case \"wont_fix\":\n return t(\"panel.statusWontFix\");\n default:\n return status;\n }\n}\n","import type { FeedbackResponse } from \"@siteping/core\";\nimport { el, onClickOutside, parseSvg, setText } from \"./dom-utils.js\";\nimport { isolateFromHost } from \"./host-isolation.js\";\nimport type { TFunction } from \"./i18n/index.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_EXPORT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>`;\n\nconst ICON_CSV = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/><line x1=\"9\" y1=\"3\" x2=\"9\" y2=\"21\"/><line x1=\"15\" y1=\"3\" x2=\"15\" y2=\"21\"/></svg>`;\n\nconst ICON_JSON = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 3H6a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2 2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h2\"/><path d=\"M16 3h2a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2 2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const EXPORT_CSS = /* css */ `.sp-export-btn{padding:5px 12px;border-radius:var(--sp-radius-full);border:1px solid var(--sp-border);background:transparent;color:var(--sp-text-tertiary);font-family:var(--sp-font);font-size:11px;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:4px;transition:all 0.2s ease;position:relative;}.sp-export-btn svg{width:13px;height:13px;}.sp-export-btn:hover{border-color:var(--sp-accent);color:var(--sp-accent-text);background:var(--sp-accent-light);}.sp-export-btn:disabled{opacity:0.5;cursor:not-allowed;pointer-events:none;}.sp-export-menu{position:absolute;top:calc(100% + 6px);right:0;min-width:180px;padding:4px;border-radius:var(--sp-radius);background:var(--sp-glass-bg-heavy);backdrop-filter:blur(var(--sp-blur));-webkit-backdrop-filter:blur(var(--sp-blur));border:1px solid var(--sp-glass-border);box-shadow:var(--sp-shadow-lg);z-index:10;opacity:0;transform:translateY(-4px) scale(0.97);transition:opacity 0.15s ease,transform 0.15s ease;pointer-events:none;}.sp-export-menu--open{opacity:1;transform:translateY(0) scale(1);pointer-events:auto;}.sp-export-option{display:flex;align-items:center;gap:10px;width:100%;padding:8px 16px;border:none;border-radius:8px;background:transparent;color:var(--sp-text-secondary);font-family:var(--sp-font);font-size:13px;font-weight:500;cursor:pointer;transition:all 0.15s ease;text-align:left;}.sp-export-option:hover,.sp-export-option:focus-visible{background:var(--sp-accent-light);color:var(--sp-accent-text);}.sp-export-option-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;}.sp-export-option-icon svg{width:16px;height:16px;}.sp-export-option-label{flex:1;}@media (forced-colors: active){.sp-export-btn,.sp-export-option,.sp-export-menu{border:2px solid ButtonText !important;background:Canvas !important;color:ButtonText !important;}.sp-export-btn:focus-visible,.sp-export-option:focus-visible{outline:3px solid Highlight !important;}}`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n;\n\n// ---------------------------------------------------------------------------\n// CSV / JSON conversion\n// ---------------------------------------------------------------------------\n\nconst CSV_COLUMNS = [\n \"id\",\n \"type\",\n \"status\",\n \"message\",\n \"url\",\n \"authorName\",\n \"authorEmail\",\n \"createdAt\",\n \"resolvedAt\",\n \"viewport\",\n] as const;\n\n/**\n * Escape a value for CSV. Two concerns, applied in order:\n *\n * 1. **Formula injection** — spreadsheet apps (Excel, Google Sheets,\n * LibreOffice) treat a cell starting with `=`, `+`, `-`, `@`, or a leading\n * TAB/CR as a formula. Columns like `message`, `authorName`, and `url` are\n * arbitrary end-user input, so a payload such as `=HYPERLINK(\"http://evil\",\n * A1)` or `=cmd|'/c calc'!A1` would execute when a reviewer opens the export.\n * We neutralize it by prefixing a single quote (the OWASP-recommended guard),\n * which forces the cell to be treated as text.\n * 2. **RFC-4180 quoting** — wrap in double-quotes (doubling inner quotes) if the\n * guarded value contains commas, quotes, or newlines.\n */\nfunction escapeCsvField(value: string): string {\n const guarded = /^[=+\\-@\\t\\r]/.test(value) ? `'${value}` : value;\n if (guarded.includes('\"') || guarded.includes(\",\") || guarded.includes(\"\\n\") || guarded.includes(\"\\r\")) {\n return `\"${guarded.replace(/\"/g, '\"\"')}\"`;\n }\n return guarded;\n}\n\n/** Convert feedbacks to CSV string */\nexport function feedbacksToCsv(feedbacks: FeedbackResponse[]): string {\n const header = CSV_COLUMNS.join(\",\");\n const rows = feedbacks.map((fb) =>\n CSV_COLUMNS.map((col) => {\n const raw = fb[col];\n return escapeCsvField(raw == null ? \"\" : String(raw));\n }).join(\",\"),\n );\n return [header, ...rows].join(\"\\n\");\n}\n\n/** Convert feedbacks to formatted JSON string */\nexport function feedbacksToJson(feedbacks: FeedbackResponse[]): string {\n return JSON.stringify(feedbacks, null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Download helper\n// ---------------------------------------------------------------------------\n\n/** Trigger browser download of a string as file */\nexport function downloadFile(content: string, filename: string, mimeType: string): void {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = \"none\";\n // The synthetic click bubbles like a real one: a host modal the export ran\n // over must not read it as an outside click.\n isolateFromHost(anchor);\n document.body.appendChild(anchor);\n anchor.click();\n // Clean up after a tick to ensure the download starts\n requestAnimationFrame(() => {\n URL.revokeObjectURL(url);\n anchor.remove();\n });\n}\n\n// ---------------------------------------------------------------------------\n// ExportButton component\n// ---------------------------------------------------------------------------\n\nexport class ExportButton {\n readonly element: HTMLElement;\n\n private menu: HTMLElement;\n private isOpen = false;\n private removeOutsideClick: (() => void) | null = null;\n\n constructor(\n _colors: ThemeColors,\n private readonly getFeedbacks: () => FeedbackResponse[],\n t: TFunction,\n ) {\n // Wrapper for relative positioning of the menu\n this.element = el(\"div\", { style: \"position: relative; display: inline-flex;\" });\n\n // Trigger button — matches .sp-btn-delete-all pill style\n const btn = document.createElement(\"button\");\n btn.className = \"sp-export-btn\";\n btn.setAttribute(\"aria-haspopup\", \"true\");\n btn.setAttribute(\"aria-expanded\", \"false\");\n btn.appendChild(parseSvg(ICON_EXPORT));\n const label = document.createElement(\"span\");\n setText(label, t(\"export.label\"));\n btn.appendChild(label);\n btn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggle();\n });\n\n // Dropdown menu\n this.menu = el(\"div\", { class: \"sp-export-menu\" });\n this.menu.setAttribute(\"role\", \"menu\");\n\n // CSV option\n const csvOption = this.createOption(ICON_CSV, t(\"export.csv\"), () => {\n this.exportAs(\"csv\");\n });\n\n // JSON option\n const jsonOption = this.createOption(ICON_JSON, t(\"export.json\"), () => {\n this.exportAs(\"json\");\n });\n\n this.menu.appendChild(csvOption);\n this.menu.appendChild(jsonOption);\n\n this.element.appendChild(btn);\n this.element.appendChild(this.menu);\n\n this.element.addEventListener(\"keydown\", (e) => {\n if (e.key !== \"Escape\" || !this.isOpen) return;\n e.stopPropagation(); // Close the menu only, not the panel\n this.close();\n btn.focus();\n });\n }\n\n private createOption(iconSvg: string, labelText: string, onClick: () => void): HTMLButtonElement {\n const option = document.createElement(\"button\");\n option.className = \"sp-export-option\";\n option.setAttribute(\"role\", \"menuitem\");\n\n const iconWrap = el(\"span\", { class: \"sp-export-option-icon\" });\n iconWrap.appendChild(parseSvg(iconSvg));\n\n const labelEl = el(\"span\", { class: \"sp-export-option-label\" });\n setText(labelEl, labelText);\n\n option.appendChild(iconWrap);\n option.appendChild(labelEl);\n\n option.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onClick();\n this.close();\n });\n\n return option;\n }\n\n private toggle(): void {\n this.isOpen ? this.close() : this.open();\n }\n\n private open(): void {\n this.isOpen = true;\n this.removeOutsideClick = onClickOutside(this.element, () => this.close());\n this.menu.classList.add(\"sp-export-menu--open\");\n const btn = this.element.querySelector<HTMLButtonElement>(\".sp-export-btn\");\n btn?.setAttribute(\"aria-expanded\", \"true\");\n }\n\n private close(): void {\n this.isOpen = false;\n this.removeOutsideClick?.();\n this.removeOutsideClick = null;\n this.menu.classList.remove(\"sp-export-menu--open\");\n const btn = this.element.querySelector<HTMLButtonElement>(\".sp-export-btn\");\n btn?.setAttribute(\"aria-expanded\", \"false\");\n }\n\n private exportAs(format: \"csv\" | \"json\"): void {\n const feedbacks = this.getFeedbacks();\n if (feedbacks.length === 0) return;\n\n const projectName = feedbacks[0]?.projectName ?? \"feedbacks\";\n const date = new Date().toISOString().slice(0, 10);\n const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\n if (format === \"csv\") {\n // A leading byte-order mark is how Excel tells a UTF-8 file from its\n // legacy code page (Windows-1252 in Western Europe): without it, every\n // accented character opens garbled. The MIME charset isn't stored in\n // the downloaded file.\n const content = `\\uFEFF${feedbacksToCsv(feedbacks)}`;\n downloadFile(content, `feedbacks-${safeName}-${date}.csv`, \"text/csv;charset=utf-8\");\n } else {\n const content = feedbacksToJson(feedbacks);\n downloadFile(content, `feedbacks-${safeName}-${date}.json`, \"application/json;charset=utf-8\");\n }\n }\n\n destroy(): void {\n this.close();\n this.element.remove();\n }\n}\n","/**\n * Bulk Actions system for the feedback panel.\n *\n * Allows multi-select on feedback cards with a floating action bar\n * for batch resolve/delete operations. Glassmorphism design with\n * spring animations and smooth transitions.\n */\n\nimport { el, hasFocusWithin, parseSvg, setButtonLoading, setHidden, setInert, setText } from \"./dom-utils.js\";\nimport { type TFunction, tWithParams } from \"./i18n/index.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// SVG Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_CHECKBOX = `<svg viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\"><rect x=\"1\" y=\"1\" width=\"16\" height=\"16\" rx=\"4\" stroke=\"currentColor\" stroke-width=\"2\"/></svg>`;\n\nexport const ICON_CHECKBOX_CHECKED = `<svg viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\"><rect x=\"1\" y=\"1\" width=\"16\" height=\"16\" rx=\"4\" fill=\"url(#sp-cb-grad)\" stroke=\"none\"/><polyline points=\"5 9 8 12 13 6\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><defs><linearGradient id=\"sp-cb-grad\" x1=\"0\" y1=\"0\" x2=\"18\" y2=\"18\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0%\" stop-color=\"var(--sp-accent)\"/><stop offset=\"100%\" stop-color=\"var(--sp-accent-dark)\"/></linearGradient></defs></svg>`;\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const BULK_CSS = /* css */ `.sp-bulk-checkbox{position:relative;width:16px;height:16px;flex-shrink:0;cursor:pointer;border-radius:4px;color:var(--sp-border);opacity:0;transition:opacity 0.15s ease,color 0.15s ease,transform 0.15s ease;display:inline-flex;align-items:center;justify-content:center;}.sp-bulk-checkbox svg{width:16px;height:16px;display:block;}.sp-bulk-checkbox:hover{color:var(--sp-accent-text);transform:scale(1.1);}.sp-bulk-checkbox--checked{color:var(--sp-accent-text);opacity:1 !important;filter:drop-shadow(0 0 4px var(--sp-accent-glow));}.sp-card:hover .sp-bulk-checkbox{opacity:1;}.sp-list--has-selection .sp-bulk-checkbox{opacity:1;}.sp-card--selected{border-left:3px solid var(--sp-accent) !important;background:var(--sp-accent-light) !important;}.sp-card--selected:hover{background:var(--sp-accent-light) !important;}.sp-bulk-select-all{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:4px;border-radius:var(--sp-radius);background:transparent;cursor:pointer;opacity:0;transition:opacity 0.2s ease,background 0.2s ease;user-select:none;font-family:var(--sp-font);font-size:12px;font-weight:500;color:var(--sp-text-secondary);}.sp-bulk-select-all:hover{background:var(--sp-bg-hover);}.sp-list:hover .sp-bulk-select-all,.sp-list--has-selection .sp-bulk-select-all{opacity:1;}.sp-bulk-select-all .sp-bulk-checkbox{opacity:1;}@keyframes sp-bulk-bar-in{from{transform:translateY(100%) scale(0.95);opacity:0;}to{transform:translateY(0) scale(1);opacity:1;}}@keyframes sp-bulk-bar-out{from{transform:translateY(0) scale(1);opacity:1;}to{transform:translateY(100%) scale(0.95);opacity:0;}}.sp-bulk-bar{position:absolute;bottom:16px;left:16px;right:16px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-radius:16px;background:var(--sp-glass-bg-heavy);backdrop-filter:blur(var(--sp-blur-heavy));-webkit-backdrop-filter:blur(var(--sp-blur-heavy));border:1px solid var(--sp-glass-border);box-shadow:var(--sp-shadow-xl);z-index:10;pointer-events:none;opacity:0;transform:translateY(100%) scale(0.95);transition:transform 0.35s cubic-bezier(0.34,1.56,0.64,1),opacity 0.25s ease;font-family:var(--sp-font);}.sp-bulk-bar--visible{pointer-events:auto;opacity:1;transform:translateY(0) scale(1);}.sp-bulk-bar-count{font-size:13px;font-weight:600;color:var(--sp-text);white-space:nowrap;letter-spacing:-0.01em;}.sp-bulk-bar-actions{display:flex;align-items:center;gap:6px;}.sp-bulk-btn-resolve,.sp-bulk-btn-delete{padding:7px 14px;border-radius:var(--sp-radius-full);border:1.5px solid transparent;background:transparent;font-family:var(--sp-font);font-size:12px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:5px;transition:all 0.2s ease;white-space:nowrap;}.sp-bulk-btn-resolve{color:#22c55e;border-color:#22c55e;}.sp-bulk-btn-resolve:hover{background:rgba(34,197,94,0.1);box-shadow:0 0 12px rgba(34,197,94,0.15);}.sp-bulk-btn-resolve:active{transform:scale(0.96);transition-duration:0.1s;}.sp-bulk-btn-delete{color:#ef4444;border-color:#ef4444;}.sp-bulk-btn-delete:hover{background:rgba(239,68,68,0.1);box-shadow:0 0 12px rgba(239,68,68,0.15);}.sp-bulk-btn-delete:active{transform:scale(0.96);transition-duration:0.1s;}.sp-bulk-btn-resolve:disabled,.sp-bulk-btn-delete:disabled{opacity:0.5;cursor:not-allowed;pointer-events:none;}.sp-bulk-btn-deselect{width:28px;height:28px;border-radius:var(--sp-radius-full);border:1px solid var(--sp-border);background:transparent;color:var(--sp-text-tertiary);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease;flex-shrink:0;padding:0;}.sp-bulk-btn-deselect:hover{background:var(--sp-bg-hover);color:var(--sp-text);border-color:var(--sp-text-tertiary);}.sp-bulk-btn-deselect:active{transform:scale(0.92);transition-duration:0.1s;}.sp-bulk-btn-deselect svg{width:12px;height:12px;}.sp-bulk-btn-resolve .sp-spinner,.sp-bulk-btn-delete .sp-spinner{width:14px;height:14px;}@media (forced-colors: active){.sp-bulk-checkbox,.sp-bulk-btn-resolve,.sp-bulk-btn-delete,.sp-bulk-btn-deselect,.sp-bulk-bar{border:2px solid ButtonText !important;background:Canvas !important;color:ButtonText !important;}.sp-bulk-checkbox--checked{background:Highlight !important;color:HighlightText !important;}.sp-card--selected{border-left:4px solid Highlight !important;}}@media (prefers-reduced-motion: reduce){.sp-bulk-bar{transition-duration:0.01ms !important;}.sp-bulk-checkbox{transition-duration:0.01ms !important;}}`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n;\n\n// ---------------------------------------------------------------------------\n// Callbacks\n// ---------------------------------------------------------------------------\n\n/** What the panel's triage actions need — reviewer mode withholds both. */\nexport type TriagePermission = \"canChangeStatus\" | \"canDelete\";\n\nexport interface BulkActionCallbacks {\n onResolve: (ids: string[]) => Promise<void>;\n onDelete: (ids: string[]) => Promise<void>;\n /** False when a selected feedback refuses it — the bar hides that action. */\n permits?: (ids: string[], permission: TriagePermission) => boolean;\n}\n\n// ---------------------------------------------------------------------------\n// BulkActions Class\n// ---------------------------------------------------------------------------\n\nexport class BulkActions {\n /** The floating action bar element — append to panel root */\n readonly barElement: HTMLElement;\n\n private selected = new Set<string>();\n private checkboxMap = new Map<string, HTMLElement>();\n private countLabel: HTMLElement;\n private resolveBtn: HTMLButtonElement;\n private deleteBtn: HTMLButtonElement;\n private selectAllCheckbox: HTMLElement | null = null;\n private listContainer: HTMLElement | null = null;\n private isProcessing = false;\n private readonly t: TFunction;\n\n constructor(\n _colors: ThemeColors,\n private readonly callbacks: BulkActionCallbacks,\n t: TFunction,\n ) {\n this.t = t;\n // ----- Build floating bar -----\n this.barElement = el(\"div\", { class: \"sp-bulk-bar\" });\n this.barElement.setAttribute(\"role\", \"toolbar\");\n this.barElement.setAttribute(\"aria-label\", t(\"bulk.aria\"));\n // Closed until something is selected (see `updateBar`).\n setInert(this.barElement, true);\n\n // Left: count label\n this.countLabel = el(\"span\", { class: \"sp-bulk-bar-count\" });\n setText(this.countLabel, tWithParams(this.t, \"bulk.selected\", { count: 0 }));\n\n // Right: action buttons\n const actions = el(\"div\", { class: \"sp-bulk-bar-actions\" });\n\n this.resolveBtn = document.createElement(\"button\");\n this.resolveBtn.className = \"sp-bulk-btn-resolve\";\n this.resolveBtn.type = \"button\";\n this.resolveBtn.addEventListener(\"click\", () => this.handleResolve());\n\n this.deleteBtn = document.createElement(\"button\");\n this.deleteBtn.className = \"sp-bulk-btn-delete\";\n this.deleteBtn.type = \"button\";\n this.deleteBtn.addEventListener(\"click\", () => this.handleDelete());\n\n const deselectBtn = document.createElement(\"button\");\n deselectBtn.className = \"sp-bulk-btn-deselect\";\n deselectBtn.type = \"button\";\n deselectBtn.setAttribute(\"aria-label\", this.t(\"bulk.deselect\"));\n deselectBtn.appendChild(\n parseSvg(\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`,\n ),\n );\n deselectBtn.addEventListener(\"click\", () => this.deselectAll());\n\n actions.appendChild(this.resolveBtn);\n actions.appendChild(this.deleteBtn);\n actions.appendChild(deselectBtn);\n\n this.barElement.appendChild(this.countLabel);\n this.barElement.appendChild(actions);\n\n // Initial button text\n this.updateButtonLabels();\n }\n\n /**\n * Create a checkbox element for a card. Returns the checkbox wrapper.\n * `label` is the card's accessible name, so a screen reader names a\n * feedback a person recognises, never its id.\n */\n createCheckbox(feedbackId: string, label: string): HTMLElement {\n const wrapper = el(\"div\", { class: \"sp-bulk-checkbox\" });\n wrapper.setAttribute(\"role\", \"checkbox\");\n wrapper.setAttribute(\"tabindex\", \"0\");\n wrapper.setAttribute(\"aria-label\", tWithParams(this.t, \"bulk.selectFeedback\", { label }));\n\n // Click handler\n wrapper.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggle(feedbackId);\n });\n\n // Keyboard: space/enter to toggle\n wrapper.addEventListener(\"keydown\", (e) => {\n if ((e as KeyboardEvent).key === \" \" || (e as KeyboardEvent).key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n this.toggle(feedbackId);\n }\n });\n\n this.checkboxMap.set(feedbackId, wrapper);\n // Re-renders (sort, group, load more) keep the selection — reflect it.\n this.updateCheckbox(feedbackId);\n return wrapper;\n }\n\n /** Whether a feedback is currently selected (for initial card state on render). */\n isSelected(feedbackId: string): boolean {\n return this.selected.has(feedbackId);\n }\n\n /**\n * Create a \"Select all\" bar element.\n * The caller should insert this at the top of the list container.\n */\n createSelectAllBar(feedbackIds: string[], label: string): HTMLElement {\n const wrapper = el(\"div\", { class: \"sp-bulk-select-all\" });\n\n // Operable from the keyboard like the card checkboxes, and where focus\n // lands when the bulk bar closes under it.\n const checkbox = el(\"div\", { class: \"sp-bulk-checkbox\" });\n checkbox.setAttribute(\"role\", \"checkbox\");\n checkbox.setAttribute(\"tabindex\", \"0\");\n checkbox.setAttribute(\"aria-label\", label);\n this.selectAllCheckbox = checkbox;\n this.updateSelectAllCheckbox(feedbackIds.length > 0 && feedbackIds.every((id) => this.selected.has(id)));\n\n const labelEl = el(\"span\");\n setText(labelEl, label);\n\n wrapper.appendChild(checkbox);\n wrapper.appendChild(labelEl);\n\n const toggleAll = () => {\n // If all selected, deselect; otherwise select all\n if (this.selected.size === feedbackIds.length && feedbackIds.length > 0) {\n this.deselectAll();\n } else {\n this.selectAll(feedbackIds);\n }\n };\n wrapper.addEventListener(\"click\", toggleAll);\n checkbox.addEventListener(\"keydown\", (e) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n toggleAll();\n }\n });\n\n return wrapper;\n }\n\n /** Set the list container reference (for toggling .sp-list--has-selection) */\n setListContainer(container: HTMLElement): void {\n this.listContainer = container;\n }\n\n /** Toggle selection for a feedback */\n toggle(feedbackId: string): void {\n if (this.isProcessing) return;\n\n if (this.selected.has(feedbackId)) {\n this.selected.delete(feedbackId);\n } else {\n this.selected.add(feedbackId);\n }\n this.updateCheckbox(feedbackId);\n this.updateBar();\n this.updateSelectAllCheckbox();\n this.updateListSelectionClass();\n this.updateCardSelectedState(feedbackId);\n }\n\n /** Select all from the given list */\n selectAll(feedbackIds: string[]): void {\n if (this.isProcessing) return;\n\n for (const id of feedbackIds) {\n this.selected.add(id);\n this.updateCheckbox(id);\n this.updateCardSelectedState(id);\n }\n this.updateBar();\n this.updateSelectAllCheckbox();\n this.updateListSelectionClass();\n }\n\n /** Clear all selections */\n deselectAll(): void {\n const prevSelected = [...this.selected];\n this.selected.clear();\n for (const id of prevSelected) {\n this.updateCheckbox(id);\n this.updateCardSelectedState(id);\n }\n this.updateBar();\n this.updateSelectAllCheckbox();\n this.updateListSelectionClass();\n }\n\n /** Get currently selected IDs */\n get selectedIds(): string[] {\n return [...this.selected];\n }\n\n /** Get selection count */\n get count(): number {\n return this.selected.size;\n }\n\n /** Whether any items are selected */\n get hasSelection(): boolean {\n return this.selected.size > 0;\n }\n\n /** Reset state (e.g., after feedbacks reload) */\n reset(): void {\n this.selected.clear();\n this.checkboxMap.clear();\n this.selectAllCheckbox = null;\n\n this.isProcessing = false;\n this.updateBar();\n this.updateListSelectionClass();\n }\n\n /** Destroy / cleanup */\n destroy(): void {\n this.selected.clear();\n this.checkboxMap.clear();\n this.selectAllCheckbox = null;\n\n this.listContainer = null;\n this.barElement.remove();\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n /** Update the bar visibility and counts */\n private updateBar(): void {\n const count = this.selected.size;\n const visible = count > 0;\n\n // The bar slides out but stays rendered: out of the tab order while\n // closed. Focus on one of its buttons (Deselect, a finished bulk action)\n // would drop to the page — hand it to the select-all checkbox instead.\n if (!visible && hasFocusWithin(this.barElement)) this.selectAllCheckbox?.focus();\n setInert(this.barElement, !visible);\n this.barElement.classList.toggle(\"sp-bulk-bar--visible\", visible);\n setText(this.countLabel, tWithParams(this.t, \"bulk.selected\", { count }));\n // Acting on part of a selection would surprise: an action some selected feedback refuses is not offered.\n const ids = [...this.selected];\n setHidden(this.resolveBtn, this.callbacks.permits?.(ids, \"canChangeStatus\") === false);\n setHidden(this.deleteBtn, this.callbacks.permits?.(ids, \"canDelete\") === false);\n this.updateButtonLabels();\n }\n\n private updateButtonLabels(): void {\n const count = this.selected.size;\n const resolve = this.t(\"bulk.resolve\");\n const del = this.t(\"bulk.delete\");\n\n // Resolve button\n this.resolveBtn.replaceChildren();\n const resolveLabel = document.createElement(\"span\");\n setText(resolveLabel, count > 0 ? `${resolve} ${count}` : resolve);\n this.resolveBtn.appendChild(resolveLabel);\n\n // Delete button\n this.deleteBtn.replaceChildren();\n const deleteLabel = document.createElement(\"span\");\n setText(deleteLabel, count > 0 ? `${del} ${count}` : del);\n this.deleteBtn.appendChild(deleteLabel);\n }\n\n private updateCheckbox(feedbackId: string): void {\n const checkbox = this.checkboxMap.get(feedbackId);\n if (!checkbox) return;\n\n const isChecked = this.selected.has(feedbackId);\n checkbox.classList.toggle(\"sp-bulk-checkbox--checked\", isChecked);\n checkbox.setAttribute(\"aria-checked\", String(isChecked));\n\n // Swap SVG icon\n checkbox.replaceChildren();\n checkbox.appendChild(parseSvg(isChecked ? ICON_CHECKBOX_CHECKED : ICON_CHECKBOX));\n }\n\n private updateSelectAllCheckbox(\n allSelected = this.selected.size > 0 && this.selected.size === this.checkboxMap.size,\n ): void {\n if (!this.selectAllCheckbox) return;\n\n this.selectAllCheckbox.classList.toggle(\"sp-bulk-checkbox--checked\", allSelected);\n this.selectAllCheckbox.setAttribute(\"aria-checked\", String(allSelected));\n\n this.selectAllCheckbox.replaceChildren();\n this.selectAllCheckbox.appendChild(parseSvg(allSelected ? ICON_CHECKBOX_CHECKED : ICON_CHECKBOX));\n }\n\n private updateListSelectionClass(): void {\n if (!this.listContainer) return;\n this.listContainer.classList.toggle(\"sp-list--has-selection\", this.selected.size > 0);\n }\n\n private updateCardSelectedState(feedbackId: string): void {\n if (!this.listContainer) return;\n const escapedId = CSS.escape(feedbackId);\n const card = this.listContainer.querySelector<HTMLElement>(`[data-feedback-id=\"${escapedId}\"]`);\n if (card) {\n card.classList.toggle(\"sp-card--selected\", this.selected.has(feedbackId));\n }\n }\n\n private async handleResolve(): Promise<void> {\n if (this.isProcessing || this.selected.size === 0) return;\n this.isProcessing = true;\n\n const ids = [...this.selected];\n setButtonLoading(this.resolveBtn);\n this.deleteBtn.disabled = true;\n\n try {\n await this.callbacks.onResolve(ids);\n } catch {\n return; // Keep the selection so the user can retry\n } finally {\n this.endProcessing();\n }\n this.deselectAll();\n }\n\n private async handleDelete(): Promise<void> {\n if (this.isProcessing || this.selected.size === 0) return;\n this.isProcessing = true;\n\n const ids = [...this.selected];\n setButtonLoading(this.deleteBtn);\n this.resolveBtn.disabled = true;\n\n try {\n await this.callbacks.onDelete(ids);\n } catch {\n return; // Keep the selection so the user can retry\n } finally {\n this.endProcessing();\n }\n this.deselectAll();\n }\n\n /**\n * Leave the loading state after success and failure alike: re-enable both\n * buttons (reset() never does) and relabel them from the current selection,\n * which the panel narrows to the failed items after a partial failure.\n */\n private endProcessing(): void {\n this.isProcessing = false;\n this.resolveBtn.disabled = false;\n this.deleteBtn.disabled = false;\n this.updateButtonLabels();\n }\n}\n","/**\n * Host-defined panel actions (`config.panelActions`) — validation, inert\n * icon parsing, URL and feedback sanitising. Everything a host hands the\n * detail view goes through here before it touches the DOM.\n */\n\nimport type { FeedbackResponse, SitepingPanelAction, SitepingPanelActionFeedback } from \"@siteping/core\";\n\n/** A validated action, its icon parsed once and cloned on every render. */\nexport interface PanelActionItem {\n readonly action: SitepingPanelAction;\n readonly icon: SVGSVGElement | null;\n}\n\n// Plain shapes plus paint/clip helpers. Everything else — script, style,\n// foreignObject, animate/set, use, image, a — is dropped, so an icon can\n// neither run code nor fetch anything.\nconst ICON_TAGS = new Set([\n \"svg\",\n \"g\",\n \"path\",\n \"circle\",\n \"ellipse\",\n \"line\",\n \"polyline\",\n \"polygon\",\n \"rect\",\n \"defs\",\n \"lineargradient\",\n \"radialgradient\",\n \"stop\",\n \"clippath\",\n \"mask\",\n]);\n\n// Geometry, paint and gradient/clip/mask plumbing. Everything else —\n// handlers, href / xlink:href, style, class, filter, marker-*, cursor — is\n// dropped.\nconst ICON_ATTRS = new Set(\n `id viewbox preserveaspectratio width height x y x1 y1 x2 y2 cx cy r rx ry fx fy fr d points pathlength\n transform opacity color fill fill-opacity fill-rule stroke stroke-width stroke-linecap stroke-linejoin\n stroke-miterlimit stroke-dasharray stroke-dashoffset stroke-opacity clip-path clip-rule mask offset\n stop-color stop-opacity gradientunits gradienttransform spreadmethod clippathunits maskunits\n maskcontentunits`.split(/\\s+/),\n);\n\n// Presentation attributes are parsed as CSS, where an escaped `\\75 rl(…)`,\n// `image-set(…)` or `src(…)` fetches as surely as `url(…)`. So a value may\n// hold no escape and call nothing but a color or transform function, or\n// `url(#id)` pointing inside the icon.\nconst INERT_CALLS =\n /\\b(?:(?:rgba?|hsla?|matrix|translate|scale|rotate|skewx|skewy)\\([^()\\\\]*\\)|url\\(\\s*(['\"]?)#[\\w-]+\\1\\s*\\))/gi;\nconst isInertValue = (value: string) => !value.includes(\"\\\\\") && !value.replace(INERT_CALLS, \"\").includes(\"(\");\n\nfunction sanitizeIconNode(node: Element): void {\n for (const attr of [...node.attributes]) {\n if (!ICON_ATTRS.has(attr.name.toLowerCase()) || !isInertValue(attr.value)) node.removeAttribute(attr.name);\n }\n for (const child of [...node.children]) {\n if (ICON_TAGS.has(child.localName.toLowerCase())) sanitizeIconNode(child);\n else child.remove();\n }\n}\n\n/**\n * Parse host SVG markup without running any of it. A `DOMParser` document is\n * inert — no scripts, no resource loads, no event handlers — unlike\n * `parseSvg`'s `createContextualFragment`, where e.g. an `<img onerror>` that\n * the HTML parser hoists out of the `<svg>` still fires. Fine for our own\n * constants, never for host input. Returns `null` when the markup is not an\n * `<svg>` element.\n */\nexport function parseActionIcon(markup: string): SVGSVGElement | null {\n const root = new DOMParser().parseFromString(markup, \"text/html\").body.firstElementChild;\n if (root?.namespaceURI !== \"http://www.w3.org/2000/svg\" || root.localName !== \"svg\") return null;\n sanitizeIconNode(root);\n root.setAttribute(\"aria-hidden\", \"true\");\n return document.importNode(root as SVGSVGElement, true);\n}\n\n/**\n * `href` resolved against the page, or `null` unless it is http(s) or\n * mailto — `javascript:`, `data:` and friends never reach an anchor. The\n * URL parser normalizes case, whitespace and control characters first.\n */\nexport function safeHref(href: string): string | null {\n try {\n const url = new URL(href, document.baseURI);\n return /^(https?|mailto):$/.test(url.protocol) ? url.href : null;\n } catch {\n return null;\n }\n}\n\n/**\n * A detached, deeply frozen copy of a feedback for host callbacks — they can\n * read everything but never mutate the records the panel renders.\n * `FeedbackResponse` is JSON by construction (`Serialized<FeedbackRecord>`),\n * so the round trip is lossless.\n */\nexport function snapshotFeedback(feedback: FeedbackResponse): SitepingPanelActionFeedback {\n return JSON.parse(JSON.stringify(feedback), (_key, value: unknown) =>\n typeof value === \"object\" && value !== null ? Object.freeze(value) : value,\n );\n}\n\nconst isButton = (a: Partial<SitepingPanelAction>) => typeof a.onAction === \"function\" && a.href === undefined;\nconst isLink = (a: Partial<SitepingPanelAction>) =>\n (typeof a.href === \"string\" || typeof a.href === \"function\") && a.onAction === undefined;\n\n/**\n * Validate `config.panelActions` once. Entries without a non-empty string\n * `id` and `label`, without exactly one of an `onAction` function or an\n * `href` (string or function), with a static `href` that `safeHref` rejects,\n * or reusing an earlier id are skipped with a console warning. An icon that\n * is not SVG markup is dropped with a warning — the action keeps its label.\n */\nexport function normalizePanelActions(actions: readonly SitepingPanelAction[] | undefined): PanelActionItem[] {\n const items: PanelActionItem[] = [];\n if (!Array.isArray(actions)) return items;\n const ids = new Set<string>();\n actions.forEach((action: Partial<SitepingPanelAction> | null | undefined, index) => {\n const where = `[siteping] panelActions[${index}]`;\n const id = action?.id;\n if (typeof id !== \"string\" || !id || typeof action?.label !== \"string\" || !action.label) {\n console.warn(`${where} ignored: it needs a non-empty string \\`id\\` and \\`label\\`.`);\n } else if (!isButton(action) && !isLink(action)) {\n console.warn(`${where} (\"${id}\") ignored: it needs either an \\`onAction\\` function or an \\`href\\`, not both.`);\n } else if (typeof action.href === \"string\" && !safeHref(action.href)) {\n console.warn(`${where} (\"${id}\") ignored: \\`href\\` must be an http(s) or mailto URL.`);\n } else if (ids.has(id)) {\n console.warn(`${where} ignored: duplicate id \"${id}\".`);\n } else {\n ids.add(id);\n const icon = action.icon === undefined ? null : parseActionIcon(action.icon);\n if (action.icon !== undefined && !icon) {\n console.warn(`${where} (\"${id}\"): \\`icon\\` is not SVG markup — showing the label only.`);\n }\n items.push({ action: action as SitepingPanelAction, icon });\n }\n });\n return items;\n}\n","/**\n * Detail View for the feedback panel.\n *\n * Slides in from the right (panel-in-panel pattern) when a user clicks\n * a feedback card, showing full details: message, metadata, annotation\n * info, status actions, and a \"Go to annotation\" button.\n *\n * Glassmorphism design — frosted glass surfaces, staggered section\n * animations, accent gradients, premium micro-interactions.\n */\n\nimport {\n type FeedbackResponse,\n type FeedbackStatus,\n isClosedStatus,\n isSafeImageUrl,\n type SitepingPanelActionFeedback,\n type SitepingPanelButtonAction,\n} from \"@siteping/core\";\nimport { el, hasFocusWithin, parseSvg, setInert, setText } from \"./dom-utils.js\";\nimport { getStatusLabel, getTypeLabel, type TFunction, tWithParams } from \"./i18n/index.js\";\nimport { ICON_USER } from \"./icons.js\";\nimport { type PanelActionItem, safeHref, snapshotFeedback } from \"./panel-actions.js\";\nimport type { TriagePermission } from \"./panel-bulk.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// SVG Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_ARROW_LEFT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"19\" y1=\"12\" x2=\"5\" y2=\"12\"/><polyline points=\"12 19 5 12 12 5\"/></svg>`;\n\nexport const ICON_MAP_PIN = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"/><circle cx=\"12\" cy=\"10\" r=\"3\"/></svg>`;\n\nexport const ICON_LINK = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>`;\n\nexport const ICON_CALENDAR = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/></svg>`;\n\nexport const ICON_MONITOR = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" ry=\"2\"/><line x1=\"8\" y1=\"21\" x2=\"16\" y2=\"21\"/><line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"/></svg>`;\n\nconst ICON_CHECK = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\n\nconst ICON_UNDO = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>`;\n\nconst ICON_TRASH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/></svg>`;\n\nconst ICON_CODE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>`;\n\nconst ICON_CROSSHAIR = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"22\" y1=\"12\" x2=\"18\" y2=\"12\"/><line x1=\"6\" y1=\"12\" x2=\"2\" y2=\"12\"/><line x1=\"12\" y1=\"6\" x2=\"12\" y2=\"2\"/><line x1=\"12\" y1=\"22\" x2=\"12\" y2=\"18\"/></svg>`;\n\nconst ICON_CHEVRON = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"9 18 15 12 9 6\"/></svg>`;\nconst ICON_TERMINAL = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"4 17 10 11 4 5\"/><line x1=\"12\" y1=\"19\" x2=\"20\" y2=\"19\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const DETAIL_CSS = /* css */ `.sp-detail{position:absolute;inset:0;display:flex;flex-direction:column;background:var(--sp-glass-bg);backdrop-filter:blur(var(--sp-blur-heavy));-webkit-backdrop-filter:blur(var(--sp-blur-heavy));z-index:20;transform:translateX(100%);transition:transform 0.35s cubic-bezier(0.16,1,0.3,1);will-change:transform;overflow:hidden;}.sp-detail--visible{transform:translateX(0);}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.sp-detail{background:var(--sp-bg);}}@supports (-webkit-backdrop-filter: blur(1px)) and (not (backdrop-filter: blur(1px))){.sp-detail{background:var(--sp-bg);}}.sp-detail-header{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid var(--sp-border);background:var(--sp-glass-bg-heavy);backdrop-filter:blur(var(--sp-blur));-webkit-backdrop-filter:blur(var(--sp-blur));flex-shrink:0;min-height:64px;}.sp-detail-back{width:40px;height:40px;border-radius:var(--sp-radius);border:none;background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--sp-text-tertiary);transition:all 0.2s ease;flex-shrink:0;padding:0;}.sp-detail-back:hover{background:var(--sp-bg-hover);color:var(--sp-text);}.sp-detail-back:active{transform:scale(0.92);transition-duration:0.1s;}.sp-detail-back svg{width:18px;height:18px;}.sp-detail-title{font-size:16px;font-weight:700;color:var(--sp-text);letter-spacing:-0.02em;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.sp-detail-header .sp-badge{flex-shrink:0;}.sp-detail-content{flex:1;overflow-y:auto;overflow-x:hidden;padding:0;}.sp-detail-content::-webkit-scrollbar{width:6px;}.sp-detail-content::-webkit-scrollbar-track{background:transparent;}.sp-detail-content::-webkit-scrollbar-thumb{background:var(--sp-border);border-radius:var(--sp-radius-full);}.sp-detail-content::-webkit-scrollbar-thumb:hover{background:var(--sp-text-tertiary);}.sp-detail-section{padding:20px 24px;border-bottom:1px solid var(--sp-border);opacity:0;transform:translateY(8px);animation:sp-detail-section-in 0.35s cubic-bezier(0.16,1,0.3,1) forwards;}@keyframes sp-detail-section-in{to{opacity:1;transform:translateY(0);}}.sp-detail-section:last-child{border-bottom:none;}.sp-detail-section-title{font-size:11px;font-weight:600;color:var(--sp-text-tertiary);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:14px;display:flex;align-items:center;gap:6px;}.sp-detail-section-title svg{width:14px;height:14px;opacity:0.6;}.sp-detail-status{display:flex;align-items:center;gap:10px;margin-bottom:16px;}.sp-detail-status-pill{display:inline-flex;align-items:center;gap:6px;padding:5px 14px;border-radius:var(--sp-radius-full);font-size:12px;font-weight:600;letter-spacing:0.02em;}.sp-detail-status-pill--open{background:rgba(34,197,94,0.1);color:#22c55e;border:1px solid rgba(34,197,94,0.2);}.sp-detail-status-pill--resolved{background:rgba(156,163,175,0.1);color:#9ca3af;border:1px solid rgba(156,163,175,0.2);}.sp-detail-status-pill--in-progress{background:rgba(245,158,11,0.1);color:#f59e0b;border:1px solid rgba(245,158,11,0.2);}.sp-detail-status-pill--wont-fix{background:rgba(148,163,184,0.1);color:#94a3b8;border:1px solid rgba(148,163,184,0.2);}.sp-detail-status-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}.sp-detail-actions{display:flex;gap:8px;}.sp-detail-actions button,.sp-detail-actions a{flex:1;height:40px;padding:0 16px;border-radius:var(--sp-radius);font-family:var(--sp-font);font-size:13px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px;transition:all 0.2s ease;}.sp-detail-actions button svg,.sp-detail-actions a svg{width:15px;height:15px;}.sp-detail-btn-resolve{border:1.5px solid #22c55e;background:rgba(34,197,94,0.06);color:#22c55e;}.sp-detail-btn-resolve:hover{background:rgba(34,197,94,0.14);box-shadow:0 0 16px rgba(34,197,94,0.12);transform:translateY(-1px);}.sp-detail-btn-resolve:active{transform:translateY(0) scale(0.98);transition-duration:0.1s;}.sp-detail-btn-reopen{border:1.5px solid var(--sp-accent);background:var(--sp-accent-light);color:var(--sp-accent-text);}.sp-detail-btn-reopen:hover{background:rgba(var(--sp-accent),0.14);box-shadow:0 0 16px var(--sp-accent-glow);transform:translateY(-1px);}.sp-detail-btn-reopen:active{transform:translateY(0) scale(0.98);transition-duration:0.1s;}.sp-detail-btn-delete{border:1.5px solid #ef4444;background:rgba(239,68,68,0.06);color:#ef4444;}.sp-detail-btn-delete:hover{background:rgba(239,68,68,0.14);box-shadow:0 0 16px rgba(239,68,68,0.12);transform:translateY(-1px);}.sp-detail-btn-delete:active{transform:translateY(0) scale(0.98);transition-duration:0.1s;}.sp-detail-btn-custom{border:1.5px solid var(--sp-border);background:var(--sp-glass-bg-heavy);color:var(--sp-text);text-decoration:none;}.sp-detail-btn-custom:hover{background:var(--sp-bg-hover);border-color:var(--sp-accent);color:var(--sp-accent-text);box-shadow:0 0 16px var(--sp-accent-glow);transform:translateY(-1px);}.sp-detail-btn-custom:active{transform:translateY(0) scale(0.98);transition-duration:0.1s;}.sp-detail-actions--custom{flex-wrap:wrap;margin-top:8px;}.sp-detail-actions--custom > .sp-detail-btn-custom{flex:1 1 auto;min-width:0;}.sp-detail-btn-custom svg{flex-shrink:0;}.sp-detail-btn-custom span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.sp-detail-actions button:disabled{opacity:0.5;cursor:not-allowed;pointer-events:none;transform:none;box-shadow:none;}.sp-detail-message{font-size:14px;line-height:1.65;color:var(--sp-text);padding:14px 16px;border-left:3px solid var(--sp-accent);border-radius:0 var(--sp-radius) var(--sp-radius) 0;background:var(--sp-glass-bg-heavy);white-space:pre-wrap;word-break:break-word;}.sp-thread-list{list-style:none;}.sp-comment{margin-top:8px;border-left-color:var(--sp-border);}.sp-comment[data-role=\"team\"]{border-left-color:var(--sp-accent);}.sp-comment-head,.sp-thread-foot{display:flex;align-items:center;gap:6px;font-size:12px;}.sp-comment-head{margin-bottom:4px;font-weight:600;}.sp-comment-head .sp-badge{background:var(--sp-accent-light);}.sp-comment-head time,.sp-thread-foot,.sp-thread-input::placeholder{font-weight:400;color:var(--sp-text-secondary);}.sp-comment-head time{margin-left:auto;}.sp-thread-input{height:auto;margin-top:10px;padding:10px 14px;}.sp-thread-foot{justify-content:space-between;margin-top:8px;}.sp-thread-foot [aria-busy=\"true\"]::before{content:\"\";display:inline-block;width:10px;height:10px;margin-right:8px;border:2px solid;border-top-color:transparent;border-radius:50%;animation:sp-spin 0.6s linear infinite;}.sp-thread-error{margin-top:8px;padding:8px 12px;border-radius:var(--sp-radius);background:var(--sp-type-bug-bg);font-size:12px;}.sp-thread-error:empty{display:none;}.sp-detail-screenshot{display:block;width:100%;height:auto;max-height:400px;object-fit:contain;border-radius:var(--sp-radius);border:1px solid var(--sp-glass-border);background:var(--sp-glass-bg-heavy);}.sp-detail-meta{display:flex;flex-direction:column;gap:14px;}.sp-detail-meta-row{display:flex;align-items:flex-start;gap:12px;}.sp-detail-meta-row svg{width:14px;height:14px;color:var(--sp-text-tertiary);flex-shrink:0;margin-top:1px;}.sp-detail-meta-content{flex:1;min-width:0;}.sp-detail-meta-label{font-size:10px;font-weight:600;color:var(--sp-text-tertiary);text-transform:uppercase;letter-spacing:0.06em;line-height:1;margin-bottom:4px;}.sp-detail-meta-value{font-size:13px;line-height:1.4;color:var(--sp-text);word-break:break-all;}.sp-detail-meta-value--mono{font-family:\"SF Mono\",\"Cascadia Code\",\"Fira Code\",\"Consolas\",monospace;font-size:12px;background:var(--sp-glass-bg-heavy);padding:2px 6px;border-radius:4px;border:1px solid var(--sp-glass-border-subtle);}.sp-detail-meta-value--secondary{color:var(--sp-text-secondary);font-size:12px;}.sp-detail-annotation{display:flex;flex-direction:column;gap:12px;}.sp-detail-annotation-info{display:flex;flex-direction:column;gap:10px;padding:14px;border-radius:var(--sp-radius);background:var(--sp-glass-bg-heavy);border:1px solid var(--sp-glass-border-subtle);}.sp-detail-annotation-row{display:flex;align-items:flex-start;gap:10px;}.sp-detail-annotation-row svg{width:13px;height:13px;color:var(--sp-text-tertiary);flex-shrink:0;margin-top:2px;}.sp-detail-annotation-label{font-size:10px;font-weight:600;color:var(--sp-text-tertiary);text-transform:uppercase;letter-spacing:0.06em;line-height:1;margin-bottom:3px;}.sp-detail-annotation-value{font-size:12px;line-height:1.4;color:var(--sp-text);word-break:break-all;}.sp-detail-annotation-value--mono{font-family:\"SF Mono\",\"Cascadia Code\",\"Fira Code\",\"Consolas\",monospace;font-size:11px;background:var(--sp-bg-hover);padding:2px 6px;border-radius:4px;display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.sp-detail-btn-goto{width:100%;height:44px;padding:0 20px;border-radius:var(--sp-radius);border:none;background:var(--sp-accent-gradient);color:#fff;font-family:var(--sp-font);font-size:14px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;transition:all 0.25s ease;box-shadow:0 2px 12px var(--sp-accent-glow);}.sp-detail-btn-goto svg{width:16px;height:16px;}.sp-detail-btn-goto:hover{box-shadow:0 4px 20px var(--sp-accent-glow);transform:translateY(-2px);}.sp-detail-btn-goto:active{transform:translateY(0) scale(0.98);transition-duration:0.1s;}@media (forced-colors: active){.sp-detail{border:2px solid ButtonText !important;background:Canvas !important;}.sp-detail-back,.sp-detail-btn-goto,.sp-detail-btn-resolve,.sp-detail-btn-reopen,.sp-detail-btn-delete,.sp-detail-btn-custom{border:2px solid ButtonText !important;background:Canvas !important;color:ButtonText !important;}.sp-detail-back:focus-visible,.sp-detail-btn-goto:focus-visible,.sp-detail-btn-resolve:focus-visible,.sp-detail-btn-reopen:focus-visible,.sp-detail-btn-delete:focus-visible,.sp-detail-btn-custom:focus-visible{outline:3px solid Highlight !important;}.sp-detail-status-pill{border:2px solid ButtonText !important;background:Canvas !important;color:ButtonText !important;}.sp-detail-message{border-left:3px solid ButtonText !important;}}.sp-detail-diag{display:flex;flex-direction:column;gap:12px;}.sp-detail-diag-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 12px;border-radius:var(--sp-radius);border:1px solid var(--sp-glass-border-subtle);background:var(--sp-glass-bg-heavy);color:var(--sp-text);font-family:var(--sp-font);font-size:12px;font-weight:600;cursor:pointer;transition:background 0.15s ease;}.sp-detail-diag-toggle:hover{background:var(--sp-bg-hover);}.sp-detail-diag-toggle svg{width:12px;height:12px;transition:transform 0.2s ease;}.sp-detail-diag-toggle[aria-expanded=\"true\"] svg{transform:rotate(90deg);}.sp-detail-diag-counts{display:inline-flex;gap:6px;font-weight:500;color:var(--sp-text-tertiary);}.sp-detail-diag-count{padding:1px 7px;border-radius:var(--sp-radius-full);background:var(--sp-bg-hover);font-variant-numeric:tabular-nums;}.sp-detail-diag-count--errors{background:rgba(239,68,68,0.14);color:#ef4444;}.sp-detail-diag-body{display:none;flex-direction:column;gap:14px;}.sp-detail-diag-body--open{display:flex;}.sp-detail-diag-group-title{font-size:10px;font-weight:700;color:var(--sp-text-tertiary);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:6px;}.sp-detail-diag-list{list-style:none;padding:0;margin:0;border-radius:var(--sp-radius);border:1px solid var(--sp-glass-border-subtle);background:var(--sp-glass-bg-heavy);max-height:240px;overflow-y:auto;}.sp-detail-diag-list li{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border-bottom:1px solid var(--sp-glass-border-subtle);font-family:\"SF Mono\",\"Cascadia Code\",\"Fira Code\",\"Consolas\",monospace;font-size:11px;line-height:1.45;color:var(--sp-text);}.sp-detail-diag-list li:last-child{border-bottom:none;}.sp-detail-diag-level{flex-shrink:0;font-weight:700;width:44px;text-transform:uppercase;letter-spacing:0.04em;font-size:10px;}.sp-detail-diag-level--log{color:var(--sp-text-tertiary);}.sp-detail-diag-level--info{color:#3b82f6;}.sp-detail-diag-level--warn{color:#f59e0b;}.sp-detail-diag-level--error{color:#ef4444;}.sp-detail-diag-message{flex:1;min-width:0;word-break:break-word;white-space:pre-wrap;}.sp-detail-diag-net{display:grid;grid-template-columns:auto 1fr auto;gap:8px;align-items:center;}.sp-detail-diag-net-status{flex-shrink:0;font-weight:700;color:#ef4444;min-width:32px;text-align:right;font-variant-numeric:tabular-nums;}.sp-detail-diag-net-method{flex-shrink:0;font-weight:600;color:var(--sp-text-tertiary);min-width:44px;}.sp-detail-diag-net-url{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--sp-text);}.sp-detail-diag-empty{padding:12px;font-style:italic;font-size:11px;color:var(--sp-text-tertiary);text-align:center;}@media (prefers-reduced-motion: reduce){.sp-detail{transition-duration:0.01ms !important;}.sp-detail-section{animation-duration:0.01ms !important;}}`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Parse a userAgent string to extract the browser name and version. */\nfunction parseBrowser(ua: string): string {\n // Order matters: Edge includes \"Edg/\", Chrome includes \"Chrome/\", etc.\n if (/Edg\\//i.test(ua)) {\n const m = ua.match(/Edg\\/([\\d.]+)/);\n return m ? `Edge ${m[1]}` : \"Edge\";\n }\n if (/OPR\\//i.test(ua) || /Opera/i.test(ua)) {\n const m = ua.match(/OPR\\/([\\d.]+)/);\n return m ? `Opera ${m[1]}` : \"Opera\";\n }\n if (/Firefox\\//i.test(ua)) {\n const m = ua.match(/Firefox\\/([\\d.]+)/);\n return m ? `Firefox ${m[1]}` : \"Firefox\";\n }\n if (/Chrome\\//i.test(ua) && !/Chromium/i.test(ua)) {\n const m = ua.match(/Chrome\\/([\\d.]+)/);\n return m ? `Chrome ${m[1]}` : \"Chrome\";\n }\n if (/Safari\\//i.test(ua) && !/Chrome/i.test(ua)) {\n const m = ua.match(/Version\\/([\\d.]+)/);\n return m ? `Safari ${m[1]}` : \"Safari\";\n }\n return \"Unknown\";\n}\n\n/** Format an ISO date string to a full date/time in the widget's locale (any BCP 47 tag, like the cards). */\nfunction formatFullDate(isoString: string, locale: string): string {\n try {\n const d = new Date(isoString);\n return d.toLocaleString(locale, {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n } catch {\n return isoString;\n }\n}\n\n/** Extract the pathname from a URL string, falling back to the raw string. */\nfunction extractPathname(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n}\n\n/** Truncate a string to a max length with ellipsis. */\nfunction truncate(str: string, max: number): string {\n if (str.length <= max) return str;\n return str.slice(0, max - 1) + \"\\u2026\";\n}\n\n/**\n * Whether a feedback carries a usable diagnostics payload. We only render the\n * section when at least one channel has entries; the section title alone with\n * two empty lists is just noise.\n */\nfunction hasDiagnostics(diagnostics: FeedbackResponse[\"diagnostics\"]): boolean {\n if (!diagnostics) return false;\n const consoleLen = Array.isArray(diagnostics.console) ? diagnostics.console.length : 0;\n const networkLen = Array.isArray(diagnostics.network) ? diagnostics.network.length : 0;\n return consoleLen > 0 || networkLen > 0;\n}\n\n/** Format a duration in ms as a compact \"123 ms\" / \"1.4 s\" string. */\nfunction formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) return \"\\u2014\";\n if (ms < 1000) return `${Math.round(ms)} ms`;\n return `${(ms / 1000).toFixed(1)} s`;\n}\n\n// ---------------------------------------------------------------------------\n// Callbacks\n// ---------------------------------------------------------------------------\n\nexport interface DetailCallbacks {\n onBack: () => void;\n onResolve: (feedback: FeedbackResponse) => Promise<void>;\n onDelete: (feedback: FeedbackResponse) => Promise<void>;\n onGoToAnnotation: (feedback: FeedbackResponse) => void;\n /** False hides \"Go to annotation\" (e.g. the feedback belongs to another page). */\n canGoToAnnotation?: (feedback: FeedbackResponse) => boolean;\n /** \"Not visible on this page\" under \"Go to annotation\", kept current by the panel. */\n lostNote?: (feedbackId: string) => HTMLElement;\n /** Runs a host-defined button action. Never rejects: the panel contains and reports host failures. */\n onCustomAction: (action: SitepingPanelButtonAction, feedback: SitepingPanelActionFeedback) => Promise<void>;\n /** Reports a host `visible()`/`href()` that threw or an unsafe computed href — the action is hidden. */\n onCustomActionError: (error: unknown) => void;\n /** The discussion thread, shown under the message — `null` when the feedback has none to show. */\n buildThread?: (feedback: FeedbackResponse) => HTMLElement | null;\n /** False leaves out Resolve/Reopen (`canChangeStatus`) or Delete (`canDelete`). */\n permits?: (feedback: FeedbackResponse, permission: TriagePermission) => boolean;\n}\n\n/** An operation in flight on a feedback: a built-in button, or the host action it runs. */\ntype PendingOp = \"resolve\" | \"delete\" | PanelActionItem;\n\n/** A host control's content: its icon, cloned from the one parsed at load, and its label. */\nfunction customContent({ action, icon }: PanelActionItem): Node[] {\n const span = document.createElement(\"span\");\n setText(span, action.label);\n return icon ? [icon.cloneNode(true), span] : [span];\n}\n\n// ---------------------------------------------------------------------------\n// DetailView Class\n// ---------------------------------------------------------------------------\n\nexport class DetailView {\n readonly element: HTMLElement;\n\n private _isVisible = false;\n /** The element focused when the view opened — `hide` gives focus back to it. */\n private opener: HTMLElement | null = null;\n private currentFeedback: FeedbackResponse | null = null;\n private readonly content: HTMLElement;\n private readonly t: TFunction;\n private readonly locale: string;\n private resolveBtn: HTMLButtonElement | null = null;\n private deleteBtn: HTMLButtonElement | null = null;\n private customBtns: HTMLButtonElement[] = [];\n /**\n * What runs on each feedback. Kept per feedback rather than per render, so\n * showing that feedback again — a panel action's `refresh()`, or coming\n * back to it — keeps the view locked until the operation settles.\n */\n private readonly pending = new Map<string, PendingOp>();\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly callbacks: DetailCallbacks,\n t: TFunction,\n locale: string,\n private readonly customActions: readonly PanelActionItem[] = [],\n ) {\n this.t = t;\n this.locale = locale;\n\n // Root container\n this.element = el(\"div\", { class: \"sp-detail\" });\n this.element.setAttribute(\"role\", \"dialog\");\n this.element.setAttribute(\"aria-label\", t(\"detail.aria\"));\n this.element.setAttribute(\"aria-hidden\", \"true\");\n // Slid off-screen while closed, but still rendered: out of the tab order.\n setInert(this.element, true);\n\n // Header (built once, title/badge updated on show())\n const header = el(\"div\", { class: \"sp-detail-header\" });\n\n const backBtn = document.createElement(\"button\");\n backBtn.type = \"button\";\n backBtn.className = \"sp-detail-back\";\n backBtn.setAttribute(\"aria-label\", this.t(\"detail.back\"));\n backBtn.appendChild(parseSvg(ICON_ARROW_LEFT));\n backBtn.addEventListener(\"click\", () => {\n this.hide();\n this.callbacks.onBack();\n });\n\n this.element.appendChild(header);\n header.appendChild(backBtn);\n\n // Title and badge are appended in show()\n\n // Scrollable content area\n this.content = el(\"div\", { class: \"sp-detail-content\" });\n this.element.appendChild(this.content);\n }\n\n /** Show the detail view for a specific feedback. */\n show(feedback: FeedbackResponse, number: number): void {\n this.currentFeedback = feedback;\n\n // ---- Update header ----\n const header = this.element.querySelector<HTMLElement>(\".sp-detail-header\");\n if (!header) return;\n // Remove old title/badge but keep the back button\n const backBtn = header.querySelector<HTMLElement>(\".sp-detail-back\");\n if (!backBtn) return;\n header.replaceChildren(backBtn);\n\n const title = el(\"span\", { class: \"sp-detail-title\" });\n setText(title, tWithParams(this.t, \"detail.title\", { number }));\n header.appendChild(title);\n\n const badge = el(\"span\", { class: \"sp-badge\" });\n badge.style.background = getTypeBgColor(feedback.type, this.colors);\n badge.style.color = getTypeColor(feedback.type, this.colors);\n setText(badge, getTypeLabel(feedback.type, this.t));\n header.appendChild(badge);\n\n // ---- Build content sections ----\n this.content.replaceChildren();\n\n let sectionIndex = 0;\n\n // Section 1: Status + Actions\n const statusSection = this.buildSection(sectionIndex++);\n this.buildStatusActions(statusSection, feedback);\n const op = this.pending.get(feedback.id);\n if (op) this.lock(op);\n this.content.appendChild(statusSection);\n\n // Section 2: Message\n const messageSection = this.buildSection(sectionIndex++);\n const messageSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n setText(messageSectionTitle, this.t(\"detail.message\"));\n messageSection.appendChild(messageSectionTitle);\n\n const messageBlock = el(\"div\", { class: \"sp-detail-message\" });\n messageBlock.style.borderLeftColor = getTypeColor(feedback.type, this.colors);\n setText(messageBlock, feedback.message);\n messageSection.appendChild(messageBlock);\n // The discussion thread reads on from the message it answers.\n const thread = this.callbacks.buildThread?.(feedback);\n if (thread) messageSection.appendChild(thread);\n this.content.appendChild(messageSection);\n\n // Section 2b: Screenshot (when captured)\n if (feedback.screenshotUrl && isSafeImageUrl(feedback.screenshotUrl)) {\n const screenshotSection = this.buildSection(sectionIndex++);\n const screenshotSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n setText(screenshotSectionTitle, this.t(\"detail.screenshot\"));\n screenshotSection.appendChild(screenshotSectionTitle);\n\n const img = document.createElement(\"img\");\n img.className = \"sp-detail-screenshot\";\n img.src = feedback.screenshotUrl;\n img.alt = this.t(\"detail.screenshotAlt\");\n img.loading = \"lazy\";\n // Avoid leaking the panel viewer's referrer to the storage host —\n // a malicious or compromised storage URL could otherwise track which\n // operators view which feedbacks.\n img.referrerPolicy = \"no-referrer\";\n screenshotSection.appendChild(img);\n this.content.appendChild(screenshotSection);\n }\n\n // Section 3: Metadata\n const metaSection = this.buildSection(sectionIndex++);\n const metaSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n setText(metaSectionTitle, this.t(\"detail.metadata\"));\n metaSection.appendChild(metaSectionTitle);\n this.buildMetadata(metaSection, feedback);\n this.content.appendChild(metaSection);\n\n // Section 4: Annotation (if any)\n if (feedback.annotations.length > 0) {\n const annSection = this.buildSection(sectionIndex++);\n const annSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n annSectionTitle.appendChild(parseSvg(ICON_MAP_PIN));\n const annTitleText = el(\"span\");\n setText(annTitleText, this.t(\"detail.annotation\"));\n annSectionTitle.appendChild(annTitleText);\n annSection.appendChild(annSectionTitle);\n this.buildAnnotation(annSection, feedback);\n this.content.appendChild(annSection);\n }\n\n // Section 5: Diagnostics (only when present + non-empty).\n // Skipped silently when the widget was launched without capture, so legacy\n // records (and hosts who never opt in) never see an empty pane.\n if (hasDiagnostics(feedback.diagnostics)) {\n const diagSection = this.buildSection(sectionIndex++);\n const diagSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n diagSectionTitle.appendChild(parseSvg(ICON_TERMINAL));\n const diagTitleText = el(\"span\");\n setText(diagTitleText, this.t(\"detail.diagnostics\"));\n diagSectionTitle.appendChild(diagTitleText);\n diagSection.appendChild(diagSectionTitle);\n this.buildDiagnostics(diagSection, feedback);\n this.content.appendChild(diagSection);\n }\n\n // ---- Show with animation ----\n // Remember what opened the view (a card), for `hide` to hand focus back.\n if (!this._isVisible) {\n const active = (this.element.getRootNode() as Document | ShadowRoot).activeElement;\n this.opener = active instanceof HTMLElement ? active : null;\n }\n this._isVisible = true;\n this.element.setAttribute(\"aria-hidden\", \"false\");\n setInert(this.element, false);\n\n // Force reflow before adding visible class to trigger CSS transition\n void this.element.offsetHeight;\n this.element.classList.add(\"sp-detail--visible\");\n\n // Focus the back button for keyboard users\n requestAnimationFrame(() => {\n backBtn.focus();\n });\n }\n\n /** Hide the detail view with slide-out animation. */\n hide(): void {\n if (!this._isVisible) return;\n this._isVisible = false;\n // Focus inside the view would drop to the page once it goes inert: back\n // to the card that opened it, or to that feedback's card if a reload\n // replaced it (a resolve, a delete keeps none).\n const refocus = hasFocusWithin(this.element);\n const id = this.currentFeedback?.id;\n this.element.classList.remove(\"sp-detail--visible\");\n this.element.setAttribute(\"aria-hidden\", \"true\");\n setInert(this.element, true);\n if (refocus) {\n const card =\n this.opener?.isConnected === true\n ? this.opener\n : id\n ? this.element.parentElement?.querySelector<HTMLElement>(`.sp-card[data-feedback-id=\"${CSS.escape(id)}\"]`)\n : null;\n card?.focus();\n }\n this.opener = null;\n this.currentFeedback = null;\n this.resolveBtn = null;\n this.deleteBtn = null;\n this.customBtns = [];\n }\n\n /** Id of the feedback on screen, `null` while hidden. */\n get feedbackId(): string | null {\n return this.currentFeedback?.id ?? null;\n }\n\n /** Whether the detail view is currently visible. */\n get isVisible(): boolean {\n return this._isVisible;\n }\n\n /** Cleanup all references. */\n destroy(): void {\n this.hide();\n this.element.remove();\n }\n\n // -----------------------------------------------------------------------\n // Private — Section builders\n // -----------------------------------------------------------------------\n\n /** Create a section element with stagger delay. */\n private buildSection(index: number): HTMLElement {\n const section = el(\"div\", { class: \"sp-detail-section\" });\n section.style.animationDelay = `${index * 40}ms`;\n return section;\n }\n\n /** Build Status pill + Resolve/Delete action buttons. */\n private buildStatusActions(container: HTMLElement, feedback: FeedbackResponse): void {\n // Closed = terminal (resolved, wont_fix): actions stay binary — closed\n // feedbacks get \"Reopen\", everything else gets \"Resolve\" — but the pill\n // always renders the record's actual status.\n const isClosed = isClosedStatus(feedback.status);\n\n // Section title\n const sectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n setText(sectionTitle, this.t(\"detail.status\"));\n container.appendChild(sectionTitle);\n\n // Status pill — one modifier class per status (open, in-progress,\n // resolved, wont-fix), each with its own dot colour.\n const statusModifier = feedback.status.replace(/_/g, \"-\");\n const dotColors: Record<FeedbackStatus, string> = {\n open: \"#22c55e\",\n in_progress: \"#f59e0b\",\n resolved: \"#9ca3af\",\n wont_fix: \"#94a3b8\",\n };\n const statusRow = el(\"div\", { class: \"sp-detail-status\" });\n const pill = el(\"span\", {\n class: `sp-detail-status-pill sp-detail-status-pill--${statusModifier}`,\n });\n const dot = el(\"span\", { class: \"sp-detail-status-dot\" });\n dot.style.background = dotColors[feedback.status] ?? dotColors.open;\n pill.appendChild(dot);\n const pillLabel = el(\"span\");\n setText(pillLabel, getStatusLabel(feedback.status, this.t));\n pill.appendChild(pillLabel);\n statusRow.appendChild(pill);\n container.appendChild(statusRow);\n\n // Action buttons\n const actions = el(\"div\", { class: \"sp-detail-actions\" });\n\n // Resolve / Reopen, and Delete — their content is what an operation restores\n this.resolveBtn = document.createElement(\"button\");\n this.resolveBtn.type = \"button\";\n this.resolveBtn.className = isClosed ? \"sp-detail-btn-reopen\" : \"sp-detail-btn-resolve\";\n this.restoreResolveBtn(feedback);\n this.resolveBtn.addEventListener(\"click\", () => this.handleResolve());\n\n this.deleteBtn = document.createElement(\"button\");\n this.deleteBtn.type = \"button\";\n this.deleteBtn.className = \"sp-detail-btn-delete\";\n this.restoreDeleteBtn();\n this.deleteBtn.addEventListener(\"click\", () => this.handleDelete());\n\n if (this.callbacks.permits?.(feedback, \"canChangeStatus\") !== false) actions.appendChild(this.resolveBtn);\n if (this.callbacks.permits?.(feedback, \"canDelete\") !== false) actions.appendChild(this.deleteBtn);\n container.appendChild(actions);\n\n this.buildCustomActions(container, feedback);\n }\n\n /**\n * Host-defined actions (`config.panelActions`) — their own wrapping row, so\n * long labels or several actions never squash Resolve/Delete.\n */\n private buildCustomActions(container: HTMLElement, feedback: FeedbackResponse): void {\n this.customBtns = [];\n if (this.customActions.length === 0) return;\n // One frozen copy per render, shared by every host callback of this view.\n const snapshot = snapshotFeedback(feedback);\n const row = el(\"div\", { class: \"sp-detail-actions sp-detail-actions--custom\" });\n for (const item of this.customActions) {\n const { action } = item;\n let control: HTMLElement;\n try {\n if (action.visible && !action.visible(snapshot)) continue;\n if (action.href === undefined) {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n // Keeps the button named while the spinner replaces its label.\n btn.setAttribute(\"aria-label\", action.label);\n btn.addEventListener(\n \"click\",\n () => void this.run(item, () => this.callbacks.onCustomAction(action, snapshot)),\n );\n this.customBtns.push(btn);\n control = btn;\n } else {\n const href = safeHref(typeof action.href === \"function\" ? action.href(snapshot) : action.href);\n if (!href) throw new Error(`[siteping] Panel action \"${action.id}\": href must be an http(s) or mailto URL.`);\n const link = document.createElement(\"a\");\n link.href = href;\n // Web links open beside the reviewed page, which never leaks as referrer.\n if (!href.startsWith(\"mailto:\")) link.target = \"_blank\";\n link.rel = \"noopener noreferrer\";\n control = link;\n }\n } catch (error) {\n this.callbacks.onCustomActionError(error);\n continue;\n }\n control.className = \"sp-detail-btn-custom\";\n control.setAttribute(\"data-action-id\", action.id);\n control.title = action.label; // full label when the row truncates it\n control.append(...customContent(item));\n row.appendChild(control);\n }\n if (row.childElementCount > 0) container.appendChild(row);\n }\n\n /** Build the metadata grid. */\n private buildMetadata(container: HTMLElement, feedback: FeedbackResponse): void {\n const meta = el(\"div\", { class: \"sp-detail-meta\" });\n\n // Page\n this.addMetaRow(meta, ICON_LINK, this.t(\"detail.page\"), () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n const pathname = extractPathname(feedback.url);\n setText(value, truncate(pathname, 60));\n value.title = feedback.url;\n return value;\n });\n\n // Author\n this.addMetaRow(meta, ICON_USER, this.t(\"detail.author\"), () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n const name = feedback.authorName || \"Anonymous\";\n const email = feedback.authorEmail;\n setText(value, email ? `${name} (${email})` : name);\n return value;\n });\n\n // Date\n this.addMetaRow(meta, ICON_CALENDAR, this.t(\"detail.date\"), () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n setText(value, formatFullDate(feedback.createdAt, this.locale));\n return value;\n });\n\n // Viewport\n this.addMetaRow(meta, ICON_MONITOR, this.t(\"detail.viewport\"), () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value sp-detail-meta-value--mono\" });\n setText(value, feedback.viewport || \"Unknown\");\n return value;\n });\n\n // Browser\n this.addMetaRow(\n meta,\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\"/><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\"/></svg>`,\n this.t(\"detail.browser\"),\n () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n setText(value, parseBrowser(feedback.userAgent));\n return value;\n },\n );\n\n // Closure date (resolvedAt is the closure timestamp for BOTH terminal\n // statuses — label it \"closed\" rather than \"resolved\" for won't-fix)\n if (feedback.resolvedAt) {\n const resolvedDate = feedback.resolvedAt;\n const dateLabel = feedback.status === \"wont_fix\" ? this.t(\"detail.closedAt\") : this.t(\"detail.resolvedAt\");\n this.addMetaRow(meta, ICON_CHECK, dateLabel, () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value sp-detail-meta-value--secondary\" });\n setText(value, formatFullDate(resolvedDate, this.locale));\n return value;\n });\n }\n\n container.appendChild(meta);\n }\n\n /** Add a single metadata row with icon, label, and custom value element. */\n private addMetaRow(container: HTMLElement, iconSvg: string, label: string, buildValue: () => HTMLElement): void {\n const row = el(\"div\", { class: \"sp-detail-meta-row\" });\n row.appendChild(parseSvg(iconSvg));\n\n const content = el(\"div\", { class: \"sp-detail-meta-content\" });\n const labelEl = el(\"div\", { class: \"sp-detail-meta-label\" });\n setText(labelEl, label);\n content.appendChild(labelEl);\n content.appendChild(buildValue());\n\n row.appendChild(content);\n container.appendChild(row);\n }\n\n /** Build the annotation detail section. */\n private buildAnnotation(container: HTMLElement, feedback: FeedbackResponse): void {\n const ann = feedback.annotations[0];\n if (!ann) return;\n\n const wrapper = el(\"div\", { class: \"sp-detail-annotation\" });\n\n // Info card\n const info = el(\"div\", { class: \"sp-detail-annotation-info\" });\n\n // Element tag\n this.addAnnotationRow(info, ICON_CODE, this.t(\"detail.element\"), () => {\n const value = el(\"span\", { class: \"sp-detail-annotation-value sp-detail-annotation-value--mono\" });\n const tagDisplay = ann.elementId ? `<${ann.elementTag}#${ann.elementId}>` : `<${ann.elementTag}>`;\n setText(value, tagDisplay);\n return value;\n });\n\n // CSS selector\n this.addAnnotationRow(info, ICON_CROSSHAIR, this.t(\"detail.selector\"), () => {\n const value = el(\"span\", { class: \"sp-detail-annotation-value sp-detail-annotation-value--mono\" });\n setText(value, truncate(ann.cssSelector, 60));\n value.title = ann.cssSelector;\n return value;\n });\n\n // Position\n this.addAnnotationRow(info, ICON_MAP_PIN, this.t(\"detail.position\"), () => {\n const value = el(\"span\", { class: \"sp-detail-annotation-value\" });\n // Rect fields are stored as fractions (0..1) of the anchor box.\n const pct = (fraction: number) => `${(fraction * 100).toFixed(1)}%`;\n setText(\n value,\n `${pct(ann.xPct)}, ${pct(ann.yPct)}` +\n (ann.wPct > 0 || ann.hPct > 0 ? ` (${pct(ann.wPct)} \\u00d7 ${pct(ann.hPct)})` : \"\"),\n );\n return value;\n });\n\n wrapper.appendChild(info);\n container.appendChild(wrapper);\n if (this.callbacks.canGoToAnnotation?.(feedback) === false) return;\n\n // \"Go to annotation\" button\n const gotoBtn = document.createElement(\"button\");\n gotoBtn.type = \"button\";\n gotoBtn.className = \"sp-detail-btn-goto\";\n gotoBtn.appendChild(parseSvg(ICON_MAP_PIN));\n const gotoLabel = document.createElement(\"span\");\n setText(gotoLabel, this.t(\"detail.goToAnnotation\"));\n gotoBtn.appendChild(gotoLabel);\n gotoBtn.addEventListener(\"click\", () => {\n if (this.currentFeedback) {\n this.callbacks.onGoToAnnotation(this.currentFeedback);\n }\n });\n\n wrapper.appendChild(gotoBtn);\n const lost = this.callbacks.lostNote?.(feedback.id);\n if (lost) wrapper.appendChild(lost);\n }\n\n /**\n * Build the collapsible Diagnostics section.\n *\n * Toggle expand/collapse via a single button; the body is hidden by default\n * to keep the detail view compact. Console entries get a colour-coded level\n * pill, network entries get a status/method/url row truncated to fit.\n */\n private buildDiagnostics(container: HTMLElement, feedback: FeedbackResponse): void {\n const diag = feedback.diagnostics;\n if (!diag) return;\n\n const consoleEntries = Array.isArray(diag.console) ? diag.console : [];\n const networkEntries = Array.isArray(diag.network) ? diag.network : [];\n const errorCount = consoleEntries.filter((e) => e.level === \"error\").length;\n\n const wrapper = el(\"div\", { class: \"sp-detail-diag\" });\n\n const toggle = document.createElement(\"button\");\n toggle.type = \"button\";\n toggle.className = \"sp-detail-diag-toggle\";\n toggle.setAttribute(\"aria-expanded\", \"false\");\n toggle.setAttribute(\"aria-label\", this.t(\"detail.diagnostics.expand\"));\n const toggleLabel = document.createElement(\"span\");\n const leftRow = document.createElement(\"span\");\n leftRow.style.display = \"inline-flex\";\n leftRow.style.alignItems = \"center\";\n leftRow.style.gap = \"8px\";\n leftRow.appendChild(parseSvg(ICON_CHEVRON));\n setText(toggleLabel, this.t(\"detail.diagnostics\"));\n leftRow.appendChild(toggleLabel);\n toggle.appendChild(leftRow);\n\n const counts = el(\"span\", { class: \"sp-detail-diag-counts\" });\n const consoleCount = el(\"span\", {\n class: `sp-detail-diag-count${errorCount > 0 ? \" sp-detail-diag-count--errors\" : \"\"}`,\n });\n setText(consoleCount, `${consoleEntries.length} console`);\n const networkCount = el(\"span\", {\n class: `sp-detail-diag-count${networkEntries.length > 0 ? \" sp-detail-diag-count--errors\" : \"\"}`,\n });\n setText(networkCount, `${networkEntries.length} net`);\n counts.appendChild(consoleCount);\n counts.appendChild(networkCount);\n toggle.appendChild(counts);\n\n const body = el(\"div\", { class: \"sp-detail-diag-body\" });\n\n // ---- Console group ----\n if (consoleEntries.length > 0) {\n const group = document.createElement(\"div\");\n const title = el(\"div\", { class: \"sp-detail-diag-group-title\" });\n setText(title, this.t(\"detail.diagnostics.console\"));\n group.appendChild(title);\n\n const list = document.createElement(\"ul\");\n list.className = \"sp-detail-diag-list\";\n for (const entry of consoleEntries) {\n const item = document.createElement(\"li\");\n const level = el(\"span\", {\n class: `sp-detail-diag-level sp-detail-diag-level--${entry.level}`,\n });\n setText(level, entry.level);\n const msg = el(\"span\", { class: \"sp-detail-diag-message\" });\n // Cap the rendered message length to keep the list readable.\n setText(msg, truncate(entry.message, 240));\n msg.title = entry.message;\n item.appendChild(level);\n item.appendChild(msg);\n list.appendChild(item);\n }\n group.appendChild(list);\n body.appendChild(group);\n }\n\n // ---- Network group ----\n if (networkEntries.length > 0) {\n const group = document.createElement(\"div\");\n const title = el(\"div\", { class: \"sp-detail-diag-group-title\" });\n setText(title, this.t(\"detail.diagnostics.network\"));\n group.appendChild(title);\n\n const list = document.createElement(\"ul\");\n list.className = \"sp-detail-diag-list\";\n for (const entry of networkEntries) {\n const item = document.createElement(\"li\");\n item.classList.add(\"sp-detail-diag-net\");\n const status = el(\"span\", { class: \"sp-detail-diag-net-status\" });\n setText(status, entry.status === 0 ? \"ERR\" : String(entry.status));\n const method = el(\"span\", { class: \"sp-detail-diag-net-method\" });\n setText(method, entry.method);\n const url = el(\"span\", { class: \"sp-detail-diag-net-url\" });\n setText(url, truncate(entry.url, 120));\n url.title = `${entry.url} — ${formatDuration(entry.durationMs)}`;\n item.appendChild(status);\n item.appendChild(method);\n item.appendChild(url);\n list.appendChild(item);\n }\n group.appendChild(list);\n body.appendChild(group);\n }\n\n toggle.addEventListener(\"click\", () => {\n const expanded = toggle.getAttribute(\"aria-expanded\") === \"true\";\n const next = !expanded;\n toggle.setAttribute(\"aria-expanded\", String(next));\n toggle.setAttribute(\n \"aria-label\",\n next ? this.t(\"detail.diagnostics.collapse\") : this.t(\"detail.diagnostics.expand\"),\n );\n body.classList.toggle(\"sp-detail-diag-body--open\", next);\n });\n\n wrapper.appendChild(toggle);\n wrapper.appendChild(body);\n container.appendChild(wrapper);\n }\n\n /** Add a single annotation info row. */\n private addAnnotationRow(\n container: HTMLElement,\n iconSvg: string,\n label: string,\n buildValue: () => HTMLElement,\n ): void {\n const row = el(\"div\", { class: \"sp-detail-annotation-row\" });\n row.appendChild(parseSvg(iconSvg));\n\n const content = el(\"div\", { class: \"sp-detail-meta-content\" });\n const labelEl = el(\"div\", { class: \"sp-detail-annotation-label\" });\n setText(labelEl, label);\n content.appendChild(labelEl);\n content.appendChild(buildValue());\n\n row.appendChild(content);\n container.appendChild(row);\n }\n\n // -----------------------------------------------------------------------\n // Private — Action handlers\n // -----------------------------------------------------------------------\n\n private handleResolve(): Promise<void> {\n return this.run(\"resolve\", (feedback) => this.callbacks.onResolve(feedback));\n }\n\n private handleDelete(): Promise<void> {\n return this.run(\"delete\", (feedback) => this.callbacks.onDelete(feedback));\n }\n\n /**\n * Run `op` on the feedback on screen, keeping the view locked — on every\n * render of that feedback — until it settles. A successful Resolve/Delete\n * hides the view (the panel does); otherwise the view comes back, unless\n * it moved on to another feedback, whose buttons are not ours to touch.\n */\n private async run(op: PendingOp, task: (feedback: FeedbackResponse) => Promise<void>): Promise<void> {\n const feedback = this.currentFeedback;\n if (!feedback || this.pending.has(feedback.id)) return;\n this.pending.set(feedback.id, op);\n this.lock(op);\n try {\n await task(feedback);\n } catch {\n // The panel reports built-in failures; host actions never reject.\n } finally {\n this.pending.delete(feedback.id);\n if (this.currentFeedback?.id === feedback.id) this.unlock(op, this.currentFeedback);\n }\n }\n\n /** `op`'s button shows a spinner and every other action is disabled. */\n private lock(op: PendingOp): void {\n this.setActionsDisabled(true);\n const btn = this.buttonFor(op);\n if (!btn) return; // a host action this render no longer shows\n this.setButtonLoading(btn);\n btn.setAttribute(\"aria-busy\", \"true\");\n }\n\n /** Undo {@link lock} on the render of `feedback` that is on screen. */\n private unlock(op: PendingOp, feedback: FeedbackResponse): void {\n const btn = this.buttonFor(op);\n btn?.removeAttribute(\"aria-busy\");\n if (op === \"resolve\") this.restoreResolveBtn(feedback);\n else if (op === \"delete\") this.restoreDeleteBtn();\n else btn?.replaceChildren(...customContent(op));\n this.setActionsDisabled(false);\n }\n\n /** This render's button for `op`, if it shows one. */\n private buttonFor(op: PendingOp): HTMLButtonElement | null {\n if (op === \"resolve\") return this.resolveBtn;\n if (op === \"delete\") return this.deleteBtn;\n return this.customBtns.find((b) => b.dataset.actionId === op.action.id) ?? null;\n }\n\n /** Enable/disable every action button of the current view. */\n private setActionsDisabled(disabled: boolean): void {\n for (const b of [this.resolveBtn, this.deleteBtn, ...this.customBtns]) {\n if (b) b.disabled = disabled;\n }\n }\n\n private setButtonLoading(btn: HTMLButtonElement): void {\n btn.disabled = true;\n btn.replaceChildren(el(\"div\", { class: \"sp-spinner sp-spinner--sm\" }));\n }\n\n private restoreResolveBtn(feedback: FeedbackResponse): void {\n if (!this.resolveBtn) return;\n this.resolveBtn.disabled = false;\n this.resolveBtn.replaceChildren();\n\n const isClosed = isClosedStatus(feedback.status);\n this.resolveBtn.appendChild(parseSvg(isClosed ? ICON_UNDO : ICON_CHECK));\n const span = document.createElement(\"span\");\n setText(span, isClosed ? this.t(\"detail.reopen\") : this.t(\"detail.resolve\"));\n this.resolveBtn.appendChild(span);\n }\n\n private restoreDeleteBtn(): void {\n if (!this.deleteBtn) return;\n this.deleteBtn.disabled = false;\n this.deleteBtn.replaceChildren();\n this.deleteBtn.appendChild(parseSvg(ICON_TRASH));\n const span = document.createElement(\"span\");\n setText(span, this.t(\"detail.delete\"));\n this.deleteBtn.appendChild(span);\n }\n}\n","/**\n * Sort and group-by-page controls for the feedback panel.\n *\n * Provides:\n * - Sort dropdown (newest, oldest, by-type, open-first)\n * - Group by page toggle (collapsible URL sections)\n * - Pure sort/group utility functions\n *\n * Glassmorphism design — glass surfaces, accent gradients,\n * smooth micro-interactions.\n */\n\nimport { type FeedbackResponse, type FeedbackType, isClosedStatus } from \"@siteping/core\";\nimport { el, onClickOutside, parseSvg, setText } from \"./dom-utils.js\";\nimport type { TFunction } from \"./i18n/index.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type SortMode = \"newest\" | \"oldest\" | \"by-type\" | \"open-first\";\n\n// ---------------------------------------------------------------------------\n// SVG Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_SORT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M11 5h10\"/><path d=\"M11 9h7\"/><path d=\"M11 13h4\"/><path d=\"M3 17l3 3 3-3\"/><path d=\"M6 18V4\"/></svg>`;\n\nexport const ICON_PAGE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/><polyline points=\"10 9 9 9 8 9\"/></svg>`;\n\nexport const ICON_CHEVRON = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"9 18 15 12 9 6\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// Sort utilities\n// ---------------------------------------------------------------------------\n\n/** Type priority for \"by-type\" sort: question, change, bug, other */\nconst TYPE_ORDER: Record<FeedbackType, number> = {\n question: 0,\n change: 1,\n bug: 2,\n other: 3,\n};\n\n/** Sort feedbacks according to the given mode. Returns a new array. */\nexport function sortFeedbacks(feedbacks: FeedbackResponse[], mode: SortMode): FeedbackResponse[] {\n const sorted = [...feedbacks];\n\n switch (mode) {\n case \"newest\":\n sorted.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n break;\n\n case \"oldest\":\n sorted.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n break;\n\n case \"by-type\":\n sorted.sort((a, b) => {\n const typeA = TYPE_ORDER[a.type] ?? 99;\n const typeB = TYPE_ORDER[b.type] ?? 99;\n if (typeA !== typeB) return typeA - typeB;\n // Within same type: newest first\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n });\n break;\n\n case \"open-first\":\n sorted.sort((a, b) => {\n // Still-actionable (0: open, in_progress) before closed (1: resolved, wont_fix)\n const statusA = isClosedStatus(a.status) ? 1 : 0;\n const statusB = isClosedStatus(b.status) ? 1 : 0;\n if (statusA !== statusB) return statusA - statusB;\n // Within same status: newest first\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n });\n break;\n }\n\n return sorted;\n}\n\n// ---------------------------------------------------------------------------\n// Group by page\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the pathname from a full URL string.\n * Falls back to the raw string if URL parsing fails.\n */\nfunction extractPathname(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n}\n\n/** Truncate a path string, keeping the beginning and end visible. */\nfunction truncatePath(path: string, maxLength: number): string {\n if (path.length <= maxLength) return path;\n const ellipsis = \"\\u2026\";\n const keep = Math.floor((maxLength - 1) / 2);\n return path.slice(0, keep) + ellipsis + path.slice(-keep);\n}\n\n/**\n * Group feedbacks by URL pathname.\n * Returns a Map sorted by feedback count descending (most feedbacks first).\n * Each group's feedbacks maintain their original order.\n */\nexport function groupFeedbacksByPage(feedbacks: FeedbackResponse[]): Map<string, FeedbackResponse[]> {\n const groups = new Map<string, FeedbackResponse[]>();\n\n for (const fb of feedbacks) {\n const path = extractPathname(fb.url);\n const existing = groups.get(path);\n if (existing) {\n existing.push(fb);\n } else {\n groups.set(path, [fb]);\n }\n }\n\n // Sort by count descending\n const sorted = new Map([...groups.entries()].sort((a, b) => b[1].length - a[1].length));\n\n return sorted;\n}\n\n// ---------------------------------------------------------------------------\n// Page group header\n// ---------------------------------------------------------------------------\n\n/**\n * Create a collapsible page group header element.\n * Click toggles the associated `.sp-group-content` sibling.\n */\nexport function createPageGroupHeader(pagePath: string, count: number): HTMLElement {\n const header = el(\"div\", { class: \"sp-group-header\" });\n header.setAttribute(\"role\", \"button\");\n header.setAttribute(\"tabindex\", \"0\");\n header.setAttribute(\"aria-expanded\", \"true\");\n header.style.borderBottomColor = \"var(--sp-border)\";\n\n // Chevron\n const chevronWrap = el(\"span\", { class: \"sp-group-header-chevron\" });\n chevronWrap.appendChild(parseSvg(ICON_CHEVRON));\n header.appendChild(chevronWrap);\n\n // Page icon\n const pageIcon = el(\"span\", { class: \"sp-group-header-icon\" });\n pageIcon.appendChild(parseSvg(ICON_PAGE));\n header.appendChild(pageIcon);\n\n // Path\n const pathEl = el(\"span\", { class: \"sp-group-header-path\" });\n const displayPath = truncatePath(pagePath, 40);\n setText(pathEl, displayPath);\n if (pagePath.length > 40) {\n pathEl.title = pagePath;\n }\n header.appendChild(pathEl);\n\n // Count badge\n const countEl = el(\"span\", { class: \"sp-group-header-count\" });\n countEl.style.background = \"var(--sp-accent-light)\";\n countEl.style.color = \"var(--sp-accent)\";\n setText(countEl, String(count));\n header.appendChild(countEl);\n\n // Toggle behavior\n const toggle = () => {\n const isExpanded = header.getAttribute(\"aria-expanded\") === \"true\";\n header.setAttribute(\"aria-expanded\", String(!isExpanded));\n header.classList.toggle(\"sp-group-header--collapsed\", isExpanded);\n\n const content = header.nextElementSibling;\n if (content?.classList.contains(\"sp-group-content\")) {\n content.classList.toggle(\"sp-group-content--collapsed\", isExpanded);\n }\n };\n\n header.addEventListener(\"click\", toggle);\n header.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n toggle();\n }\n });\n\n return header;\n}\n\n// ---------------------------------------------------------------------------\n// PanelSortControls\n// ---------------------------------------------------------------------------\n\nexport class PanelSortControls {\n readonly element: HTMLElement;\n\n private _sortMode: SortMode = \"newest\";\n private _groupByPage = false;\n private menuEl: HTMLElement | null = null;\n private sortBtn: HTMLButtonElement;\n private readonly sortDropdown: HTMLElement;\n private groupToggle: HTMLButtonElement;\n private readonly t: TFunction;\n private readonly onChange: () => void;\n private removeOutsideClick: (() => void) | null = null;\n\n constructor(onChange: () => void, t: TFunction) {\n this.onChange = onChange;\n this.t = t;\n\n this.element = el(\"div\", { class: \"sp-sort-controls\" });\n\n // Sort button\n this.sortBtn = document.createElement(\"button\");\n this.sortBtn.className = \"sp-sort-btn\";\n this.sortBtn.setAttribute(\"aria-haspopup\", \"listbox\");\n this.sortBtn.setAttribute(\"aria-expanded\", \"false\");\n\n const sortIcon = parseSvg(ICON_SORT);\n this.sortBtn.appendChild(sortIcon);\n\n this.sortBtn.appendChild(el(\"span\", { class: \"sp-sort-btn-label\" }));\n this.updateSortLabel();\n\n this.sortBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggleMenu();\n });\n\n // Group by page toggle\n this.groupToggle = document.createElement(\"button\");\n this.groupToggle.className = \"sp-group-toggle\";\n this.groupToggle.setAttribute(\"aria-pressed\", \"false\");\n\n const groupIcon = parseSvg(ICON_PAGE);\n this.groupToggle.appendChild(groupIcon);\n\n const groupLabel = el(\"span\", { class: \"sp-group-toggle-label\" });\n setText(groupLabel, this.t(\"group.byPage\"));\n this.groupToggle.appendChild(groupLabel);\n\n this.groupToggle.addEventListener(\"click\", () => {\n this._groupByPage = !this._groupByPage;\n this.groupToggle.classList.toggle(\"sp-group-toggle--active\", this._groupByPage);\n this.groupToggle.setAttribute(\"aria-pressed\", String(this._groupByPage));\n this.onChange();\n });\n\n // The trigger and its menu: a click anywhere else closes the menu.\n this.sortDropdown = el(\"div\", { class: \"sp-sort-dropdown\" });\n this.sortDropdown.appendChild(this.sortBtn);\n\n this.element.appendChild(this.sortDropdown);\n this.element.appendChild(this.groupToggle);\n\n // Close the open menu on Escape from anywhere in the controls: a click\n // leaves focus on the trigger, not in the menu.\n this.element.addEventListener(\"keydown\", (e) => {\n if (e.key !== \"Escape\" || !this.menuEl) return;\n e.stopPropagation(); // Close the menu only, not the panel\n this.closeMenu();\n this.sortBtn.focus();\n });\n }\n\n get sortMode(): SortMode {\n return this._sortMode;\n }\n\n get groupByPage(): boolean {\n return this._groupByPage;\n }\n\n private toggleMenu(): void {\n if (this.menuEl) {\n this.closeMenu();\n return;\n }\n this.openMenu();\n }\n\n private openMenu(): void {\n this.menuEl = el(\"div\", { class: \"sp-sort-menu\" });\n this.menuEl.setAttribute(\"role\", \"listbox\");\n this.menuEl.setAttribute(\"aria-label\", this.t(\"sort.label\"));\n this.sortBtn.setAttribute(\"aria-expanded\", \"true\");\n\n const options: { mode: SortMode; label: string }[] = [\n { mode: \"newest\", label: this.t(\"sort.newest\") },\n { mode: \"oldest\", label: this.t(\"sort.oldest\") },\n { mode: \"by-type\", label: this.t(\"sort.byType\") },\n { mode: \"open-first\", label: this.t(\"sort.openFirst\") },\n ];\n\n for (const opt of options) {\n const item = document.createElement(\"button\");\n item.className = `sp-sort-option${opt.mode === this._sortMode ? \" sp-sort-option--active\" : \"\"}`;\n item.setAttribute(\"role\", \"option\");\n item.setAttribute(\"aria-selected\", String(opt.mode === this._sortMode));\n\n if (opt.mode === this._sortMode) {\n item.style.background = \"var(--sp-accent-light)\";\n item.style.color = \"var(--sp-accent-text)\";\n }\n\n setText(item, opt.label);\n\n item.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this._sortMode = opt.mode;\n this.updateSortLabel();\n this.closeMenu();\n this.onChange();\n });\n\n this.menuEl.appendChild(item);\n }\n\n // Position relative to button\n this.sortDropdown.appendChild(this.menuEl);\n\n // Close on outside click. Armed now, not in a frame that closeMenu()\n // could miss: capture listeners added while the opening click is at its\n // target never see that click.\n this.removeOutsideClick = onClickOutside(this.sortDropdown, () => this.closeMenu());\n }\n\n private closeMenu(): void {\n if (this.menuEl) {\n this.menuEl.remove();\n this.menuEl = null;\n }\n this.sortBtn.setAttribute(\"aria-expanded\", \"false\");\n this.removeOutsideClick?.();\n this.removeOutsideClick = null;\n }\n\n private updateSortLabel(): void {\n const labelMap: Record<SortMode, string> = {\n newest: this.t(\"sort.newest\"),\n oldest: this.t(\"sort.oldest\"),\n \"by-type\": this.t(\"sort.byType\"),\n \"open-first\": this.t(\"sort.openFirst\"),\n };\n const text = labelMap[this._sortMode];\n const label = this.sortBtn.querySelector(\".sp-sort-btn-label\");\n if (label) setText(label as HTMLElement, text);\n // The name holds the visible mode, which speech users read off the button (WCAG 2.5.3).\n this.sortBtn.setAttribute(\"aria-label\", `${this.t(\"sort.label\")}: ${text}`);\n }\n\n destroy(): void {\n this.closeMenu();\n }\n}\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const SORT_CSS = /* css */ `.sp-sort-controls{display:flex;align-items:center;gap:6px;margin-top:4px;padding-top:8px;border-top:1px solid var(--sp-border);}.sp-sort-dropdown{display:contents;}.sp-sort-btn{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:var(--sp-radius-full);border:1px solid var(--sp-border);background:var(--sp-glass-bg-heavy);color:var(--sp-text-secondary);font-family:var(--sp-font);font-size:12px;font-weight:500;cursor:pointer;white-space:nowrap;transition:all 0.2s ease;position:relative;}.sp-sort-btn svg{width:14px;height:14px;flex-shrink:0;}.sp-sort-btn:hover{border-color:var(--sp-accent);color:var(--sp-accent-text);background:var(--sp-accent-light);}.sp-sort-btn[aria-expanded=\"true\"]{border-color:var(--sp-accent);color:var(--sp-accent-text);background:var(--sp-accent-light);}.sp-sort-menu{position:absolute;top:calc(100% + 6px);left:0;min-width:170px;padding:4px;border-radius:var(--sp-radius);background:var(--sp-glass-bg-heavy);backdrop-filter:blur(var(--sp-blur-heavy));-webkit-backdrop-filter:blur(var(--sp-blur-heavy));border:1px solid var(--sp-glass-border);box-shadow:var(--sp-shadow-md);z-index:10;animation:sp-sort-menu-in 0.15s ease-out both;}@keyframes sp-sort-menu-in{from{opacity:0;transform:translateY(-4px) scale(0.97);}to{opacity:1;transform:translateY(0) scale(1);}}.sp-sort-option{display:block;width:100%;padding:8px 12px;border:none;border-radius:8px;background:transparent;color:var(--sp-text-secondary);font-family:var(--sp-font);font-size:12px;font-weight:500;cursor:pointer;text-align:left;transition:all 0.15s ease;}.sp-sort-option:hover{background:var(--sp-bg-hover);color:var(--sp-text);}.sp-sort-option--active{font-weight:600;}.sp-sort-option--active:hover{background:var(--sp-accent-light);color:var(--sp-accent-text);}.sp-group-toggle,.sp-mine-toggle{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:var(--sp-radius-full);border:1px solid var(--sp-border);background:var(--sp-glass-bg-heavy);color:var(--sp-text-secondary);font-family:var(--sp-font);font-size:12px;font-weight:500;cursor:pointer;white-space:nowrap;transition:all 0.2s ease;}.sp-group-toggle svg,.sp-mine-toggle svg{width:13px;height:13px;flex-shrink:0;}.sp-group-toggle:hover,.sp-mine-toggle:hover{border-color:var(--sp-accent);color:var(--sp-accent-text);background:var(--sp-accent-light);}.sp-group-toggle--active,.sp-mine-toggle--active{background:var(--sp-accent-gradient);border-color:transparent;color:#fff;box-shadow:0 2px 8px var(--sp-accent-glow);}.sp-group-toggle--active:hover,.sp-mine-toggle--active:hover{background:var(--sp-accent-gradient);border-color:transparent;color:#fff;}.sp-group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--sp-accent-light);border-bottom:1px solid var(--sp-border);cursor:pointer;user-select:none;position:sticky;top:0;z-index:2;transition:background 0.2s ease;}.sp-group-header:hover{background:var(--sp-bg-hover);}.sp-group-header:focus-visible{outline:2px solid var(--sp-accent);outline-offset:-2px;}.sp-group-header-chevron{display:flex;align-items:center;justify-content:center;width:16px;height:16px;flex-shrink:0;transition:transform 0.2s ease;transform:rotate(90deg);}.sp-group-header-chevron svg{width:12px;height:12px;color:var(--sp-text-tertiary);}.sp-group-header--collapsed .sp-group-header-chevron{transform:rotate(0deg);}.sp-group-header-icon{display:flex;align-items:center;flex-shrink:0;}.sp-group-header-icon svg{width:14px;height:14px;color:var(--sp-text-tertiary);}.sp-group-header-path{font-size:12px;font-weight:600;color:var(--sp-text-secondary);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.sp-group-header-count{font-size:11px;font-weight:700;padding:1px 8px;border-radius:var(--sp-radius-full);flex-shrink:0;font-variant-numeric:tabular-nums;}.sp-group-content{overflow:hidden;transition:max-height 0.25s ease,opacity 0.2s ease;max-height:5000px;opacity:1;}.sp-group-content--collapsed{max-height:0;opacity:0;pointer-events:none;}@media (forced-colors: active){.sp-sort-btn,.sp-group-toggle,.sp-mine-toggle,.sp-sort-option,.sp-group-header{border:2px solid ButtonText !important;background:Canvas !important;color:ButtonText !important;}.sp-group-toggle--active,.sp-mine-toggle--active{background:Highlight !important;color:HighlightText !important;}.sp-sort-btn:focus-visible,.sp-group-toggle:focus-visible,.sp-mine-toggle:focus-visible,.sp-sort-option:focus-visible,.sp-group-header:focus-visible{outline:3px solid Highlight !important;}.sp-sort-menu{border:2px solid ButtonText !important;background:Canvas !important;}}@media (prefers-reduced-motion: reduce){.sp-sort-menu{animation:none;}.sp-group-header-chevron{transition:none;}.sp-group-content{transition:none;}}`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n;\n","import { type FeedbackResponse, isClosedStatus } from \"@siteping/core\";\nimport { el, setText } from \"./dom-utils.js\";\nimport { type TFunction, tWithParams } from \"./i18n/index.js\";\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const STATS_CSS = /* css */ `.sp-stats-bar{display:flex;flex-direction:column;gap:8px;padding:12px 24px;border-bottom:1px solid var(--sp-border);user-select:none;}.sp-stats-bar[hidden]{display:none;}.sp-stats-row{display:flex;align-items:center;gap:16px;}.sp-stats-item{display:flex;align-items:center;gap:6px;}.sp-stats-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}.sp-stats-value{font-size:16px;font-weight:600;line-height:1;color:var(--sp-text);font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\";transition:opacity 0.3s ease;}.sp-stats-label{font-size:11px;line-height:1;color:var(--sp-text-tertiary);text-transform:uppercase;letter-spacing:0.04em;}.sp-stats-progress{display:flex;align-items:center;gap:8px;}.sp-stats-progress-track{flex:1;height:4px;border-radius:2px;background:var(--sp-border);overflow:hidden;}.sp-stats-progress-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--sp-accent),#22c55e);width:0%;transition:width 0.5s ease;}.sp-stats-progress-label{font-size:10px;line-height:1;color:var(--sp-text-tertiary);white-space:nowrap;font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\";min-width:64px;text-align:right;}`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n;\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Compact statistics bar displayed between filters and the feedback list.\n * Shows open/closed/bug counts and a closed-percentage progress bar.\n */\nexport class PanelStats {\n readonly element: HTMLElement;\n\n private readonly valueOpen: HTMLElement;\n private readonly valueClosed: HTMLElement;\n private readonly valueBugs: HTMLElement;\n private readonly progressFill: HTMLElement;\n private readonly progressLabel: HTMLElement;\n private readonly t: TFunction;\n\n constructor(t: TFunction) {\n this.t = t;\n // Container\n this.element = el(\"div\", { class: \"sp-stats-bar\" });\n this.element.setAttribute(\"aria-label\", t(\"stats.aria\"));\n this.element.hidden = true;\n\n // --- Stats row ---\n const row = el(\"div\", { class: \"sp-stats-row\" });\n\n // Open\n const itemOpen = el(\"div\", { class: \"sp-stats-item\" });\n const dotOpen = el(\"span\", { class: \"sp-stats-dot\" });\n dotOpen.style.background = \"#22c55e\";\n this.valueOpen = el(\"span\", { class: \"sp-stats-value\" });\n setText(this.valueOpen, \"0\");\n const labelOpen = el(\"span\", { class: \"sp-stats-label\" });\n setText(labelOpen, this.t(\"stats.open\"));\n itemOpen.appendChild(dotOpen);\n itemOpen.appendChild(this.valueOpen);\n itemOpen.appendChild(labelOpen);\n\n // Closed (resolved + wont_fix): never called \"resolved\" (#302)\n const itemClosed = el(\"div\", { class: \"sp-stats-item\" });\n const dotClosed = el(\"span\", { class: \"sp-stats-dot\" });\n dotClosed.style.background = \"#9ca3af\";\n this.valueClosed = el(\"span\", { class: \"sp-stats-value\" });\n setText(this.valueClosed, \"0\");\n const labelClosed = el(\"span\", { class: \"sp-stats-label\" });\n setText(labelClosed, this.t(\"stats.closed\"));\n itemClosed.appendChild(dotClosed);\n itemClosed.appendChild(this.valueClosed);\n itemClosed.appendChild(labelClosed);\n\n // Bugs\n const itemBugs = el(\"div\", { class: \"sp-stats-item\" });\n const dotBugs = el(\"span\", { class: \"sp-stats-dot\" });\n dotBugs.style.background = \"var(--sp-type-bug)\";\n this.valueBugs = el(\"span\", { class: \"sp-stats-value\" });\n setText(this.valueBugs, \"0\");\n const labelBugs = el(\"span\", { class: \"sp-stats-label\" });\n setText(labelBugs, this.t(\"stats.bugs\"));\n itemBugs.appendChild(dotBugs);\n itemBugs.appendChild(this.valueBugs);\n itemBugs.appendChild(labelBugs);\n\n row.appendChild(itemOpen);\n row.appendChild(itemClosed);\n row.appendChild(itemBugs);\n\n // --- Progress bar ---\n const progress = el(\"div\", { class: \"sp-stats-progress\" });\n const track = el(\"div\", { class: \"sp-stats-progress-track\" });\n this.progressFill = el(\"div\", { class: \"sp-stats-progress-fill\" });\n track.appendChild(this.progressFill);\n this.progressLabel = el(\"span\", { class: \"sp-stats-progress-label\" });\n setText(this.progressLabel, \"\");\n progress.appendChild(track);\n progress.appendChild(this.progressLabel);\n\n this.element.appendChild(row);\n this.element.appendChild(progress);\n }\n\n /** Update stats from current feedbacks array + total count. */\n update(feedbacks: FeedbackResponse[], total: number): void {\n // Hide when there are no feedbacks at all\n if (total === 0) {\n this.element.hidden = true;\n return;\n }\n this.element.hidden = false;\n\n let openCount = 0;\n let closedCount = 0;\n let bugCount = 0;\n\n for (const fb of feedbacks) {\n // Binary client-facing buckets: \"open\" = anything still actionable\n // (open, in_progress), \"closed\" = resolved or wont_fix.\n if (isClosedStatus(fb.status)) closedCount++;\n else openCount++;\n if (fb.type === \"bug\") bugCount++;\n }\n\n setText(this.valueOpen, String(openCount));\n setText(this.valueClosed, String(closedCount));\n setText(this.valueBugs, String(bugCount));\n\n // Percentage closed (relative to visible feedbacks, not total)\n const visible = feedbacks.length;\n const pct = visible > 0 ? Math.round((closedCount / visible) * 100) : 0;\n\n // Animate fill width via rAF so the transition kicks in after the DOM update\n requestAnimationFrame(() => {\n this.progressFill.style.width = `${pct}%`;\n });\n\n const progressText = tWithParams(this.t, \"stats.progress\", { percent: pct });\n setText(this.progressLabel, progressText);\n }\n}\n","/**\n * Keyboard shortcuts system for the feedback panel.\n *\n * Gmail/GitHub-style single-key shortcuts:\n * J/K — navigate cards, R — resolve, D — delete,\n * F or / — focus search, X — toggle select, ? — help overlay.\n *\n * Active only when the panel is open. Ignores keypresses inside\n * input/textarea elements. Uses a Map<string, handler> for O(1) lookup.\n */\n\nimport { el, hasFocusWithin, parseSvg, setInert, setText } from \"./dom-utils.js\";\nimport type { TFunction, Translations } from \"./i18n/index.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// Icon\n// ---------------------------------------------------------------------------\n\nexport const ICON_KEYBOARD = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\"/><path d=\"M6 8h.01\"/><path d=\"M10 8h.01\"/><path d=\"M14 8h.01\"/><path d=\"M18 8h.01\"/><path d=\"M6 12h.01\"/><path d=\"M18 12h.01\"/><path d=\"M8 16h8\"/></svg>`;\n\n// All translation keys are sourced from the central i18n dict via t().\ntype ShortcutsI18nKey = Extract<keyof Translations, `shortcuts.${string}`>;\n\n// ---------------------------------------------------------------------------\n// Callbacks\n// ---------------------------------------------------------------------------\n\nexport interface ShortcutCallbacks {\n onNavigate: (direction: \"up\" | \"down\") => void;\n onResolve: () => void;\n onDelete: () => void;\n onFocusSearch: () => void;\n onToggleSelect: () => void;\n /** True while another layer (e.g. the detail view) covers the list. */\n isSuspended?: () => boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Card focus helpers\n// ---------------------------------------------------------------------------\n\n/** Get the currently focused card index from the list container (-1 if none). */\nexport function getFocusedCardIndex(listContainer: HTMLElement): number {\n const cards = Array.from(listContainer.querySelectorAll<HTMLElement>(\".sp-card\"));\n // Real focus wins over the J/K highlight — a card reached by Tab or click\n // must not leave R/D acting on the last J/K target.\n const root = listContainer.getRootNode() as Document | ShadowRoot;\n const activeCard = root.activeElement?.closest<HTMLElement>(\".sp-card\");\n const activeIdx = activeCard ? cards.indexOf(activeCard) : -1;\n if (activeIdx >= 0) return activeIdx;\n return cards.findIndex((card) => card.classList.contains(\"sp-card--focused\"));\n}\n\n/** Focus a card by index in the list container. Clamps to valid range. */\nexport function focusCardByIndex(listContainer: HTMLElement, index: number): void {\n const cards = listContainer.querySelectorAll<HTMLElement>(\".sp-card\");\n if (cards.length === 0) return;\n\n // Remove previous focus\n for (const card of cards) {\n card.classList.remove(\"sp-card--focused\");\n }\n\n const clamped = Math.max(0, Math.min(index, cards.length - 1));\n const target = cards[clamped];\n if (!target) return;\n target.classList.add(\"sp-card--focused\");\n target.scrollIntoView({ block: \"nearest\", behavior: \"smooth\" });\n target.focus({ preventScroll: true });\n}\n\n// ---------------------------------------------------------------------------\n// Shortcut definitions (used to build help grid)\n// ---------------------------------------------------------------------------\n\ninterface ShortcutDef {\n keys: string[];\n label: ShortcutsI18nKey;\n}\n\nconst SHORTCUT_DEFS: ShortcutDef[] = [\n { keys: [\"J\", \"K\"], label: \"shortcuts.navigate\" },\n { keys: [\"R\"], label: \"shortcuts.resolve\" },\n { keys: [\"D\"], label: \"shortcuts.delete\" },\n { keys: [\"F\", \"/\"], label: \"shortcuts.search\" },\n { keys: [\"X\"], label: \"shortcuts.select\" },\n { keys: [\"?\"], label: \"shortcuts.help\" },\n { keys: [\"Esc\"], label: \"shortcuts.close\" },\n];\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const SHORTCUTS_CSS = /* css */ `.sp-shortcuts-overlay{position:fixed;inset:0;background:var(--sp-backdrop, rgba(15, 23, 42, 0.2));backdrop-filter:blur(var(--sp-blur));-webkit-backdrop-filter:blur(var(--sp-blur));display:flex;align-items:center;justify-content:center;z-index:10;opacity:0;pointer-events:none;transition:opacity 0.2s ease;}.sp-shortcuts-overlay--visible{opacity:1;pointer-events:auto;}.sp-shortcuts-card{width:380px;max-width:calc(100vw - 32px);padding:24px 28px 20px;border-radius:20px;background:var(--sp-glass-bg-heavy);backdrop-filter:blur(var(--sp-blur-heavy));-webkit-backdrop-filter:blur(var(--sp-blur-heavy));border:1px solid var(--sp-glass-border);box-shadow:var(--sp-shadow-xl);font-family:var(--sp-font);position:relative;transform:scale(0.92) translateY(8px);transition:transform 0.25s cubic-bezier(0.16,1,0.3,1);}.sp-shortcuts-overlay--visible .sp-shortcuts-card{transform:scale(1) translateY(0);}.sp-shortcuts-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:700;color:var(--sp-text);margin-bottom:18px;}.sp-shortcuts-title svg{width:18px;height:18px;color:var(--sp-text-secondary);flex-shrink:0;}.sp-shortcuts-close{position:absolute;top:16px;right:16px;width:28px;height:28px;border-radius:8px;border:none;background:transparent;color:var(--sp-text-tertiary);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.15s ease,color 0.15s ease;}.sp-shortcuts-close:hover{background:var(--sp-bg-hover);color:var(--sp-text);}.sp-shortcuts-close svg{width:14px;height:14px;}.sp-shortcuts-grid{display:flex;flex-direction:column;gap:10px;}.sp-shortcuts-row{display:flex;align-items:center;gap:12px;}.sp-shortcuts-keys{display:flex;align-items:center;gap:4px;min-width:80px;justify-content:flex-end;}.sp-shortcuts-separator{font-size:11px;color:var(--sp-text-tertiary);user-select:none;}.sp-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:26px;padding:0 7px;border-radius:6px;background:var(--sp-bg-hover);border:1px solid var(--sp-border);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.08),0 1px 2px rgba(0,0,0,0.04);font-family:ui-monospace,\"SF Mono\",\"Cascadia Code\",\"Segoe UI Mono\",Menlo,monospace;font-size:12px;font-weight:600;color:var(--sp-text);text-align:center;line-height:1;user-select:none;}.sp-shortcuts-desc{font-size:13px;color:var(--sp-text-secondary);line-height:1.3;}.sp-shortcuts-hint{width:24px;height:24px;border-radius:var(--sp-radius-full);border:1px solid var(--sp-border);background:var(--sp-bg-hover);color:var(--sp-text-tertiary);font-family:ui-monospace,\"SF Mono\",\"Cascadia Code\",\"Segoe UI Mono\",Menlo,monospace;font-size:12px;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.15s ease,color 0.15s ease,border-color 0.15s ease;position:absolute;bottom:12px;right:12px;}.sp-shortcuts-hint:hover{background:var(--sp-accent-light);color:var(--sp-accent-text);border-color:var(--sp-accent);}.sp-shortcuts-hint::after{content:attr(aria-label);position:absolute;bottom:calc(100% + 6px);right:0;padding:4px 8px;border-radius:6px;background:var(--sp-glass-bg-heavy);border:1px solid var(--sp-glass-border);box-shadow:var(--sp-shadow-sm);font-family:var(--sp-font);font-size:11px;font-weight:500;color:var(--sp-text-secondary);white-space:nowrap;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity 0.15s ease,transform 0.15s ease;}.sp-shortcuts-hint:hover::after{opacity:1;transform:translateY(0);}.sp-card--focused{outline:2px solid var(--sp-accent);outline-offset:-2px;border-radius:inherit;}@media (prefers-reduced-motion: reduce){.sp-shortcuts-overlay,.sp-shortcuts-card,.sp-shortcuts-close,.sp-shortcuts-hint,.sp-shortcuts-hint::after{transition-duration:0.01ms !important;}}`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n;\n\n// ---------------------------------------------------------------------------\n// Close icon (small X) — reused from icons.ts pattern\n// ---------------------------------------------------------------------------\n\nconst ICON_CLOSE_SM = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// KeyboardShortcuts\n// ---------------------------------------------------------------------------\n\nexport class KeyboardShortcuts {\n /** Help overlay element — append to shadow root. */\n readonly helpOverlay: HTMLElement;\n /** Small \"?\" hint button — append inside the panel. */\n readonly hintButton: HTMLButtonElement;\n\n private readonly keyMap: Map<string, () => void>;\n private readonly isSuspended: () => boolean;\n private readonly boundHandler: (e: KeyboardEvent) => void;\n private shadowRoot: ShadowRoot | HTMLElement | null = null;\n private enabled = false;\n private helpVisible = false;\n /** The element focused when the help opened — `hideHelp` gives focus back to it. */\n private opener: HTMLElement | null = null;\n private destroyed = false;\n\n constructor(\n _colors: ThemeColors,\n callbacks: ShortcutCallbacks,\n private readonly t: TFunction,\n ) {\n // Build key map (O(1) dispatch)\n this.keyMap = new Map<string, () => void>([\n [\"j\", () => callbacks.onNavigate(\"down\")],\n [\"k\", () => callbacks.onNavigate(\"up\")],\n [\"r\", () => callbacks.onResolve()],\n [\"d\", () => callbacks.onDelete()],\n [\"f\", () => callbacks.onFocusSearch()],\n [\"/\", () => callbacks.onFocusSearch()],\n [\"x\", () => callbacks.onToggleSelect()],\n [\"?\", () => this.toggleHelp()],\n ]);\n this.isSuspended = callbacks.isSuspended ?? (() => false);\n\n // Build DOM\n this.helpOverlay = this.buildOverlay();\n this.hintButton = this.buildHintButton();\n\n // Bind handler once\n this.boundHandler = (e: KeyboardEvent) => this.handleKeydown(e);\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** Enable shortcuts. Provide the shadow root (or document) to attach the listener. */\n enable(root?: ShadowRoot | HTMLElement): void {\n if (this.destroyed || this.enabled) return;\n if (root) this.shadowRoot = root;\n const target = this.shadowRoot ?? document;\n target.addEventListener(\"keydown\", this.boundHandler as EventListener);\n this.enabled = true;\n }\n\n /** Disable shortcuts (call when panel closes). */\n disable(): void {\n if (!this.enabled) return;\n const target = this.shadowRoot ?? document;\n target.removeEventListener(\"keydown\", this.boundHandler as EventListener);\n this.enabled = false;\n // Also hide help if visible\n if (this.helpVisible) this.hideHelp();\n }\n\n /** Whether the help overlay is showing (it consumes Escape before the panel). */\n get isHelpVisible(): boolean {\n return this.helpVisible;\n }\n\n /** Show/hide help overlay. */\n toggleHelp(): void {\n if (this.helpVisible) {\n this.hideHelp();\n } else {\n this.showHelp();\n }\n }\n\n /** Destroy and clean up all listeners. */\n destroy(): void {\n if (this.destroyed) return;\n this.disable();\n this.helpOverlay.remove();\n this.hintButton.remove();\n this.destroyed = true;\n }\n\n // -------------------------------------------------------------------------\n // Keyboard handler\n // -------------------------------------------------------------------------\n\n private handleKeydown(e: KeyboardEvent): void {\n // Escape closes help overlay only (panel close handled elsewhere)\n if (e.key === \"Escape\") {\n if (this.helpVisible) {\n e.preventDefault();\n e.stopPropagation();\n this.hideHelp();\n }\n return;\n }\n\n // If help overlay is open, block all other shortcuts\n if (this.helpVisible) return;\n\n // A modal layer (confirm dialog) or a view covering the list owns the\n // keyboard — R/D must not act on a card hidden behind it.\n if (this.isSuspended()) return;\n const path = e.composedPath();\n if (path.some((n) => n instanceof Element && n.getAttribute(\"aria-modal\") === \"true\")) return;\n\n // Ignore when focus is in an input, textarea, or contenteditable\n const active = path[0] as HTMLElement | undefined;\n if (active) {\n const tag = active.tagName?.toLowerCase();\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") return;\n if (active.isContentEditable) return;\n }\n\n // Ignore modified keys (Ctrl, Alt, Meta) — except Shift for \"?\"\n if (e.ctrlKey || e.altKey || e.metaKey) return;\n\n const handler = this.keyMap.get(e.key);\n if (handler) {\n e.preventDefault();\n e.stopPropagation();\n handler();\n }\n }\n\n // -------------------------------------------------------------------------\n // Help overlay visibility\n // -------------------------------------------------------------------------\n\n private showHelp(): void {\n const active = (this.helpOverlay.getRootNode() as Document | ShadowRoot).activeElement;\n this.opener = active instanceof HTMLElement ? active : null;\n this.helpVisible = true;\n setInert(this.helpOverlay, false);\n this.helpOverlay.classList.add(\"sp-shortcuts-overlay--visible\");\n\n // Focus the close button for accessibility\n const closeBtn = this.helpOverlay.querySelector<HTMLButtonElement>(\".sp-shortcuts-close\");\n closeBtn?.focus();\n }\n\n private hideHelp(): void {\n this.helpVisible = false;\n // Back to where the overlay was opened from (the \"?\" hint, a card)\n // rather than to the page, once its close button goes inert.\n const refocus = hasFocusWithin(this.helpOverlay);\n setInert(this.helpOverlay, true);\n this.helpOverlay.classList.remove(\"sp-shortcuts-overlay--visible\");\n if (refocus && this.opener?.isConnected) this.opener.focus();\n this.opener = null;\n }\n\n // -------------------------------------------------------------------------\n // DOM builders\n // -------------------------------------------------------------------------\n\n private buildOverlay(): HTMLElement {\n const overlay = el(\"div\", { class: \"sp-shortcuts-overlay\" });\n overlay.setAttribute(\"role\", \"dialog\");\n overlay.setAttribute(\"aria-modal\", \"true\");\n overlay.setAttribute(\"aria-label\", this.t(\"shortcuts.title\"));\n // Faded out while closed, but still rendered: out of the tab order.\n setInert(overlay, true);\n\n // Click backdrop to close\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.hideHelp();\n });\n\n const card = el(\"div\", { class: \"sp-shortcuts-card\" });\n\n // Title\n const title = el(\"div\", { class: \"sp-shortcuts-title\" });\n title.appendChild(parseSvg(ICON_KEYBOARD));\n const titleText = el(\"span\");\n setText(titleText, this.t(\"shortcuts.title\"));\n title.appendChild(titleText);\n card.appendChild(title);\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"sp-shortcuts-close\";\n closeBtn.setAttribute(\"aria-label\", this.t(\"shortcuts.close\"));\n closeBtn.appendChild(parseSvg(ICON_CLOSE_SM));\n closeBtn.addEventListener(\"click\", () => this.hideHelp());\n card.appendChild(closeBtn);\n\n // Grid\n const grid = el(\"div\", { class: \"sp-shortcuts-grid\" });\n\n for (const def of SHORTCUT_DEFS) {\n const row = el(\"div\", { class: \"sp-shortcuts-row\" });\n\n const keysWrap = el(\"div\", { class: \"sp-shortcuts-keys\" });\n def.keys.forEach((key, i) => {\n if (i > 0) {\n const sep = el(\"span\", { class: \"sp-shortcuts-separator\" });\n setText(sep, \"/\");\n keysWrap.appendChild(sep);\n }\n const kbd = el(\"span\", { class: \"sp-kbd\" });\n setText(kbd, key);\n keysWrap.appendChild(kbd);\n });\n\n const desc = el(\"span\", { class: \"sp-shortcuts-desc\" });\n setText(desc, this.t(def.label));\n\n row.appendChild(keysWrap);\n row.appendChild(desc);\n grid.appendChild(row);\n }\n\n card.appendChild(grid);\n overlay.appendChild(card);\n\n return overlay;\n }\n\n private buildHintButton(): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.className = \"sp-shortcuts-hint\";\n btn.setAttribute(\"aria-label\", this.t(\"shortcuts.hint\"));\n setText(btn, \"?\");\n btn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggleHelp();\n });\n return btn;\n }\n}\n"]}