@beezping/adapter-memory 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -1,96 +1,152 @@
1
- import { type Prettify, type Serialized } from "./type-utils.js";
1
+ import { type DeepReadonly, type Prettify, type Serialized } from "./type-utils.js";
2
2
  /** FAB anchor — bottom-corner placement supported by the widget. */
3
- export type SitepingPosition = "bottom-right" | "bottom-left";
3
+ export type BeezpingPosition = "bottom-right" | "bottom-left";
4
4
  /** Visual theme — `auto` resolves to `light` or `dark` via system preference. */
5
- export type SitepingTheme = "light" | "dark" | "auto";
5
+ export type BeezpingTheme = "light" | "dark" | "auto";
6
6
  /** Built-in UI locales shipped with the widget. */
7
- export declare const BUILTIN_LOCALES: readonly ["en", "fr", "de", "es", "it", "pt", "ru"];
7
+ export declare const BUILTIN_LOCALES: readonly ["en", "fr", "de", "es", "it", "pt", "ru", "ja"];
8
8
  export type BuiltinLocale = (typeof BUILTIN_LOCALES)[number];
9
9
  /**
10
10
  * Locale identifier accepted by the widget. Built-in locales are kept as
11
11
  * literal strings so editors auto-complete them, but arbitrary BCP-47 tags
12
12
  * are also accepted (custom dictionaries registered via `registerLocale`).
13
13
  */
14
- export type SitepingLocale = BuiltinLocale | (string & {});
14
+ export type BeezpingLocale = BuiltinLocale | (string & {});
15
15
  /**
16
- * Reasons reported through `SitepingConfig.onSkip` — production environment,
17
- * mobile viewport, or server-side rendering (no `window`/`document`).
16
+ * Reasons reported through `BeezpingConfig.onSkip` — production environment,
17
+ * viewport narrower than `minViewportWidth` (`"mobile"`), or server-side
18
+ * rendering (no `window`/`document`).
18
19
  */
19
- export type SitepingSkipReason = "production" | "mobile" | "ssr";
20
+ export type BeezpingSkipReason = "production" | "mobile" | "ssr";
20
21
  /** Per-channel + per-buffer-size diagnostics configuration. */
21
22
  export interface DiagnosticsCaptureOptions {
22
23
  console?: boolean | undefined;
23
24
  network?: boolean | undefined;
25
+ /** Console buffer size — default and maximum 50 (the server's cap; larger values are clamped). */
24
26
  maxConsoleEntries?: number | undefined;
27
+ /** Failed-request buffer size — default and maximum 20 (the server's cap; larger values are clamped). */
25
28
  maxNetworkEntries?: number | undefined;
26
29
  }
27
30
  /** Identity payload supplied by the host application — bypasses the modal. */
28
- export interface SitepingIdentity {
31
+ export interface BeezpingIdentity {
29
32
  name: string;
30
33
  email: string;
31
34
  }
35
+ /**
36
+ * Max length of an identity's `name` and `email` — the HTTP schema's
37
+ * `authorName` / `authorEmail` cap. The widget's modal enforces it so a value
38
+ * it persists is never a 400 on every later submission.
39
+ */
40
+ export declare const IDENTITY_FIELD_MAX_LENGTH = 200;
32
41
  /** Deep-link configuration — controls how a feedback id is read from the URL. */
33
- export interface SitepingDeepLinkOptions {
34
- /** Query parameter name carrying the feedback id. Defaults to `"siteping"`. */
42
+ export interface BeezpingDeepLinkOptions {
43
+ /** Query parameter name carrying the feedback id. Defaults to `"beezping"`. */
35
44
  param?: string | undefined;
36
45
  }
37
46
  /**
38
- * Extra request headers for HTTP mode — a static map, or a factory (sync or
39
- * async) invoked once per request to produce fresh values (e.g. a short-lived
40
- * session token).
47
+ * The feedback handed to panel action callbacks: a detached, deeply frozen
48
+ * copy, typed read-only all the way down. Type your own helpers with it — a
49
+ * `FeedbackResponse` parameter does not accept the frozen copy.
41
50
  */
42
- export type SitepingHeadersOption = Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
51
+ export type BeezpingPanelActionFeedback = DeepReadonly<FeedbackResponse>;
43
52
  /**
44
- * Cookie policy for HTTP-mode requests — forwarded verbatim as the
45
- * `credentials` option of every `fetch` the widget (or the dashboard's
46
- * endpoint source) makes. Mirrors the DOM `RequestCredentials` union,
47
- * declared here so core stays free of DOM lib types.
48
- *
49
- * - `"same-origin"` (default): cookies only when the endpoint shares the
50
- * page's origin — the browser's own default.
51
- * - `"include"`: also send cookies to a cross-origin endpoint. Required when a
52
- * server on another origin authenticates with a session cookie; the server
53
- * must answer with credentialed CORS (the page's exact origin plus
54
- * `Access-Control-Allow-Credentials: true`, e.g. `@beezping/server`'s
55
- * `allowedOrigins`).
56
- * - `"omit"`: never send cookies, even same-origin.
57
- */
58
- export type SitepingRequestCredentials = "omit" | "same-origin" | "include";
59
- /** Every accepted {@link SitepingRequestCredentials} value — runtime guard source for untyped (script-tag) consumers. */
60
- export declare const REQUEST_CREDENTIALS_MODES: readonly ["omit", "same-origin", "include"];
61
- /**
62
- * Credentials mode used when none is configured — the browser's own `fetch`
63
- * default, so leaving the option unset never changes cookie behavior (and
64
- * never opts a cross-origin endpoint into cookie-carrying, CSRF-prone requests).
65
- */
66
- export declare const DEFAULT_REQUEST_CREDENTIALS = "same-origin";
67
- /**
68
- * Narrow an untyped config value to a {@link SitepingRequestCredentials} mode.
53
+ * Helpers passed to a panel action's `onAction` as its second argument.
54
+ * Both do nothing once the widget has been destroyed.
55
+ */
56
+ export interface BeezpingPanelActionContext {
57
+ /**
58
+ * Re-fetch the panel list and markers, then re-render the detail view with
59
+ * the updated feedback — or go back to the list when it no longer matches
60
+ * the panel filters. Call it after your action changed the feedback
61
+ * server-side (e.g. moved it to `in_progress` once a ticket exists).
62
+ */
63
+ refresh: () => Promise<void>;
64
+ /** Close the feedback panel. */
65
+ close: () => void;
66
+ }
67
+ /**
68
+ * Fields shared by both kinds of {@link BeezpingPanelAction}.
69
69
  *
70
- * @param value - Raw `credentials` option (may come from an untyped script-tag config).
71
- * @returns `true` when `value` is one of {@link REQUEST_CREDENTIALS_MODES}.
70
+ * Do not use this type directly — use {@link BeezpingPanelAction}.
72
71
  */
73
- export declare function isRequestCredentials(value: unknown): value is SitepingRequestCredentials;
72
+ export interface BeezpingPanelActionBase {
73
+ /** Stable, unique identifier — becomes `data-action-id` on the rendered control. */
74
+ id: string;
75
+ /**
76
+ * Visible label, rendered as plain text. Host-provided verbatim — not
77
+ * routed through the widget i18n, since hosts localize their own strings.
78
+ */
79
+ label: string;
80
+ /**
81
+ * Optional inline SVG markup rendered before the label. It is parsed
82
+ * inertly and reduced to plain shapes — scripts, event handlers, links,
83
+ * styles and external references are dropped — but keep it static markup
84
+ * you control. Markup that is not an `<svg>` is ignored with a warning.
85
+ */
86
+ icon?: string | undefined;
87
+ /**
88
+ * Per-feedback visibility predicate. Return `false` to omit the action
89
+ * for that feedback. Defaults to always visible. A throw hides the action
90
+ * and is reported through `onError`.
91
+ */
92
+ visible?: ((feedback: BeezpingPanelActionFeedback) => boolean) | undefined;
93
+ }
94
+ /** A panel action rendered as a button that runs host code. */
95
+ export interface BeezpingPanelButtonAction extends BeezpingPanelActionBase {
96
+ /**
97
+ * Invoked on click. While a returned promise is pending the detail view's
98
+ * action buttons are disabled and the clicked button shows a spinner —
99
+ * also after `context.refresh()` re-renders the view, and when the user
100
+ * comes back to that feedback.
101
+ * Throws and rejections are reported through `onError` and restore the
102
+ * buttons; the detail view stays open either way.
103
+ */
104
+ onAction: (feedback: BeezpingPanelActionFeedback, context: BeezpingPanelActionContext) => void | Promise<void>;
105
+ /** Not available on a button action — use either `onAction` or `href`, never both. */
106
+ href?: never;
107
+ }
108
+ /** A panel action rendered as a link. */
109
+ export interface BeezpingPanelLinkAction extends BeezpingPanelActionBase {
110
+ /**
111
+ * Link target — a URL, or a function building one from the feedback.
112
+ * Relative URLs resolve against the page. Only `http:`, `https:` and
113
+ * `mailto:` are rendered: a static `href` with any other scheme
114
+ * (`javascript:` included) skips the action with a warning; a function
115
+ * returning one hides the action and is reported through `onError`. Web
116
+ * links open in a new tab with `rel="noopener noreferrer"`.
117
+ */
118
+ href: string | ((feedback: BeezpingPanelActionFeedback) => string);
119
+ /** Not available on a link action — use either `onAction` or `href`, never both. */
120
+ onAction?: never;
121
+ }
74
122
  /**
75
- * Actionable description of a rejected `credentials` option — shared by the
76
- * widget init guard and the dashboard's endpoint source so both report the
77
- * same wording.
123
+ * A host-defined action rendered in the feedback detail view, below the
124
+ * built-in Resolve / Delete buttons: a button running your code
125
+ * (`onAction`) or a link (`href`) — never both.
78
126
  *
79
- * @param value - The rejected raw value (a config value, never user content).
80
- * @returns e.g. `invalid \`credentials\` "all". Expected one of "omit", "same-origin", "include".`
127
+ * Hosts use this to bridge feedbacks into their own systems — create a
128
+ * ticket, dispatch to a bot, open the feedback in their tracker — without
129
+ * forking the panel. Callbacks receive a detached, deeply frozen copy of
130
+ * the feedback: read it freely, it can never alter what the panel displays.
81
131
  */
82
- export declare function describeInvalidRequestCredentials(value: unknown): string;
132
+ export type BeezpingPanelAction = BeezpingPanelButtonAction | BeezpingPanelLinkAction;
133
+ /**
134
+ * Extra request headers for HTTP mode — a static map, or a factory (sync or
135
+ * async) invoked once per request to produce fresh values (e.g. a short-lived
136
+ * session token).
137
+ */
138
+ export type BeezpingHeadersOption = Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
83
139
  /**
84
140
  * Options shared by both widget modes (HTTP and direct store).
85
141
  *
86
- * Do not use this type directly — use {@link SitepingConfig}, the
142
+ * Do not use this type directly — use {@link BeezpingConfig}, the
87
143
  * discriminated union that adds the mode-specific fields.
88
144
  */
89
- export interface SitepingBaseConfig {
145
+ export interface BeezpingBaseConfig {
90
146
  /** Required — project identifier used to scope feedbacks */
91
147
  projectName: string;
92
148
  /** FAB position — defaults to 'bottom-right' */
93
- position?: SitepingPosition | undefined;
149
+ position?: BeezpingPosition | undefined;
94
150
  /**
95
151
  * Show the "toggle markers visibility" item in the FAB radial menu.
96
152
  * Defaults to `true` (current behavior). Set to `false` to hide that
@@ -109,7 +165,7 @@ export interface SitepingBaseConfig {
109
165
  accentColor?: string | undefined;
110
166
  /**
111
167
  * Render the widget even when it would normally be skipped — this bypasses
112
- * BOTH the production-environment guard AND the mobile-viewport guard.
168
+ * BOTH the production-environment guard AND the `minViewportWidth` guard.
113
169
  * It does NOT bypass the SSR guard: without `window`/`document` the widget
114
170
  * never renders and `onSkip("ssr")` fires instead.
115
171
  * Defaults to false. Use it for dedicated review tools, staging environments,
@@ -118,18 +174,20 @@ export interface SitepingBaseConfig {
118
174
  forceShow?: boolean | undefined;
119
175
  /**
120
176
  * Minimum viewport width (px) at or above which the widget renders. Below it,
121
- * the widget is skipped and `onSkip("mobile")` fires. Defaults to `768`.
177
+ * the widget is skipped and `onSkip("mobile")` fires. Defaults to `0`: the
178
+ * widget renders at every width, and phones get a compact layout (bottom
179
+ * sheets, touch-sized controls).
122
180
  *
123
- * Set lower (e.g. `0`) to allow narrow/mobile viewports, or use `forceShow`
124
- * to bypass the viewport check entirely.
181
+ * Set it (e.g. `768`) to keep the widget off small screens, or use
182
+ * `forceShow` to bypass the viewport check entirely.
125
183
  */
126
184
  minViewportWidth?: number | undefined;
127
185
  /** Enable debug logging of lifecycle events — defaults to false */
128
186
  debug?: boolean | undefined;
129
187
  /** Color theme — defaults to 'light' */
130
- theme?: SitepingTheme | undefined;
131
- /** UI locale — defaults to 'en'. Built-in: en, fr, de, es, it, pt (Brazilian), ru. Any other string falls back to English. */
132
- locale?: SitepingLocale | undefined;
188
+ theme?: BeezpingTheme | undefined;
189
+ /** UI locale — defaults to 'en'. Built-in: en, fr, de, es, it, pt (Brazilian), ru, ja. Any other string falls back to English. */
190
+ locale?: BeezpingLocale | undefined;
133
191
  /**
134
192
  * Returns the current page scope for annotations and panel filtering.
135
193
  * Called on initial markers load and on `instance.refresh()`.
@@ -161,7 +219,7 @@ export interface SitepingBaseConfig {
161
219
  * `html2canvas-pro` ships as a regular dependency of `@beezping/widget` so the
162
220
  * dynamic import always resolves; you don't need to install anything extra.
163
221
  *
164
- * **Masking sensitive elements:** add `data-siteping-ignore="true"` to any
222
+ * **Masking sensitive elements:** add `data-beezping-ignore="true"` to any
165
223
  * element you do NOT want captured (password fields, credit-card forms,
166
224
  * API tokens shown in the UI, etc.). The capture predicate skips matching
167
225
  * elements *and their descendants*. Do this BEFORE turning on screenshots
@@ -179,18 +237,21 @@ export interface SitepingBaseConfig {
179
237
  *
180
238
  * Keyboard-triggered context menus (≣ Menu key, Shift+F10) always get the
181
239
  * native menu; only mouse right-click and touch/pen long-press open the
182
- * composer.
240
+ * composer — a long-press only where the browser fires `contextmenu` for
241
+ * it, which iOS and iPadOS never do (see the note below).
183
242
  *
184
243
  * **Modifier-key escape hatch:** holding Shift, Ctrl, Alt, or Meta while
185
244
  * right-clicking always falls through to the native context menu, giving
186
245
  * users (and devtools) an escape hatch regardless of this setting.
187
246
  *
188
- * Right-clicks on SitePing's own UI (FAB, panel, markers, popup) are
247
+ * Right-clicks on Beezping's own UI (FAB, panel, markers, popup) are
189
248
  * ignored — the native menu is shown as expected.
190
249
  *
191
- * Note: on Android, `contextmenu` fires on long-press. The widget already
192
- * hides below `minViewportWidth` (default 768 px), but tablets above that
193
- * threshold will trigger this flow on long-press.
250
+ * Note: on Android, `contextmenu` fires on long-press — touch users open the
251
+ * composer by long-pressing, on phones too since the widget renders at every
252
+ * width by default (see `minViewportWidth`). On iPhone and iPad a long-press
253
+ * never fires `contextmenu` (WebKit bug 213953): users there open annotate
254
+ * mode from the floating button and tap the element.
194
255
  */
195
256
  enableRightClickComment?: boolean | undefined;
196
257
  /**
@@ -203,16 +264,19 @@ export interface SitepingBaseConfig {
203
264
  *
204
265
  * - `true` — capture with defaults (50 console / 20 network entries).
205
266
  * - `false` (default) — no capture, no monkey-patching.
206
- * - object — per-channel toggles + custom buffer sizes.
267
+ * - object — per-channel toggles + smaller buffer sizes (values above the
268
+ * 50 / 20 server caps are clamped so submissions never fail validation).
207
269
  *
208
270
  * **Privacy considerations:** console messages may contain anything the
209
271
  * host page logs, including user data. Failed network requests record the
210
- * URL (with query string) but never the response body. Inform end users
211
- * before enabling in environments where they might log sensitive values.
272
+ * URL without its credentials, query string or hash, and never the
273
+ * response body.
274
+ * Inform end users before enabling in environments where they might log
275
+ * sensitive values.
212
276
  */
213
277
  captureDiagnostics?: boolean | DiagnosticsCaptureOptions | undefined;
214
- /** Called when the widget is skipped (production mode, mobile viewport, SSR — no DOM) */
215
- onSkip?: (reason: SitepingSkipReason) => void;
278
+ /** Called when the widget is skipped (production mode, viewport under `minViewportWidth`, SSR — no DOM) */
279
+ onSkip?: (reason: BeezpingSkipReason) => void;
216
280
  /**
217
281
  * Auto-focus a specific annotation when its ID appears in the URL query
218
282
  * string. Lets hosts deeplink directly into a feedback from external
@@ -226,7 +290,7 @@ export interface SitepingBaseConfig {
226
290
  *
227
291
  * - `false` / `undefined` (default): no URL parsing. Existing behavior
228
292
  * unchanged, no host URL inspection.
229
- * - `true`: enabled with default query parameter name `siteping`.
293
+ * - `true`: enabled with default query parameter name `beezping`.
230
294
  * - object: enabled with a custom parameter name. Use this to avoid
231
295
  * clashes with host-app query keys.
232
296
  *
@@ -236,7 +300,7 @@ export interface SitepingBaseConfig {
236
300
  * Hosts that need re-focus on route change can call
237
301
  * `instance.focusFeedback(id)` explicitly.
238
302
  */
239
- deepLink?: boolean | SitepingDeepLinkOptions | undefined;
303
+ deepLink?: boolean | BeezpingDeepLinkOptions | undefined;
240
304
  /**
241
305
  * Automatically re-fetch feedbacks when the page changes during client-side
242
306
  * (SPA) navigation. Enabled by default.
@@ -275,26 +339,51 @@ export interface SitepingBaseConfig {
275
339
  * read at widget init time, not on every render. Hosts that need live
276
340
  * identity updates after sign-in/sign-out should currently remount the
277
341
  * widget (e.g. via a React `key` on the wrapping component). See
278
- * https://github.com/NeosiaNexus/SitePing/issues/85 for tracking a
342
+ * https://github.com/guidomodarelli/beezping/issues/85 for tracking a
279
343
  * future enhancement that propagates identity updates without a remount.
280
344
  */
281
- identity?: SitepingIdentity | undefined;
345
+ identity?: BeezpingIdentity | undefined;
346
+ /**
347
+ * Host-defined actions rendered in the feedback detail view, below the
348
+ * built-in Resolve and Delete buttons. Read once when the panel loads:
349
+ * entries without a non-empty `id` and `label`, without exactly one of
350
+ * `onAction` / `href`, with an unsafe static `href`, or reusing an earlier
351
+ * `id` are skipped with a console warning. See {@link BeezpingPanelAction}.
352
+ */
353
+ panelActions?: readonly BeezpingPanelAction[] | undefined;
354
+ /**
355
+ * Reviewer mode: hide the actions that triage feedback — resolve, reopen,
356
+ * delete, the bulk actions, "Delete all" — and keep creating, browsing
357
+ * and replying. Defaults to `false`. The server's `permissions` hide
358
+ * what it would refuse on top of it. It only hides: the server decides
359
+ * what it accepts. Read once when the panel loads.
360
+ */
361
+ readOnly?: boolean | undefined;
282
362
  /** Called when the feedback panel is opened. */
283
363
  onOpen?: (() => void) | undefined;
284
364
  /** Called when the feedback panel is closed. */
285
365
  onClose?: (() => void) | undefined;
286
366
  /** Called after a feedback is successfully submitted. */
287
367
  onFeedbackSent?: ((feedback: FeedbackResponse) => void) | undefined;
368
+ /** Called after a reply is posted from the panel's discussion thread. */
369
+ onCommentAdded?: ((comment: CommentResponse) => void) | undefined;
288
370
  /**
289
371
  * Called when a feedback API call fails.
290
372
  *
291
- * The widget always emits a `SitepingError` (or a subclass:
292
- * `SitepingNetworkError`, `SitepingValidationError`, `SitepingAuthError`)
373
+ * The widget always emits a `BeezpingError` (or a subclass:
374
+ * `BeezpingNetworkError`, `BeezpingValidationError`, `BeezpingAuthError`)
293
375
  * for HTTP-mode failures — host apps can `instanceof` to drive retry
294
376
  * logic, or read `error.code` (`"NETWORK" | "VALIDATION" | "AUTH" |
295
- * "SERVER"`) and `error.retryable`. The type is widened to `Error` so
296
- * direct-store callers can still surface raw errors without breaking the
297
- * contract.
377
+ * "SERVER"`) and `error.retryable` — and an `AUTH` error's `status`
378
+ * (`401` for missing or dead credentials, `403` for a refusal). The type
379
+ * is widened to `Error` so direct-store callers can still surface raw
380
+ * errors without breaking the contract.
381
+ *
382
+ * Also receives whatever a `panelActions` callback throws or rejects with
383
+ * (non-`Error` values are wrapped). Those host failures are not API
384
+ * failures, so they are not emitted on the public `feedback:error` event.
385
+ * The widget has no UI for them, so they are also always logged with
386
+ * `console.error`, whether or not `onError` is set.
298
387
  */
299
388
  onError?: ((error: Error) => void) | undefined;
300
389
  /** Called when the user starts drawing an annotation. */
@@ -306,8 +395,8 @@ export interface SitepingBaseConfig {
306
395
  * HTTP mode — the widget talks to a server endpoint backed by a store
307
396
  * adapter (e.g. `@beezping/adapter-prisma` request handlers).
308
397
  */
309
- export interface SitepingHttpConfig extends SitepingBaseConfig {
310
- /** HTTP endpoint that receives feedbacks (e.g. '/api/siteping'). */
398
+ export interface BeezpingHttpConfig extends BeezpingBaseConfig {
399
+ /** HTTP endpoint that receives feedbacks (e.g. '/api/beezping'). */
311
400
  endpoint: string;
312
401
  /**
313
402
  * Convenience auth for HTTP mode — sent as `Authorization: Bearer <apiKey>`
@@ -323,60 +412,44 @@ export interface SitepingHttpConfig extends SitepingBaseConfig {
323
412
  /**
324
413
  * Extra headers for every HTTP-mode request — a static map, or a factory
325
414
  * (sync or async) called once per request (e.g. to fetch a fresh session
326
- * token). Merged over the widget's generated headers, so an explicit
327
- * `Authorization` entry overrides `apiKey`. A throwing/rejecting factory
328
- * fails the request like a network error.
329
- */
330
- headers?: SitepingHeadersOption | undefined;
331
- /**
332
- * Cookie policy for every HTTP-mode request (submit, list, update, delete
333
- * and the offline retry-queue replay). Defaults to `"same-origin"` — the
334
- * browser default, so existing setups are unchanged.
335
- *
336
- * Set `"include"` when `endpoint` lives on **another origin** and the
337
- * server authenticates with a session cookie (e.g. a custom
338
- * `access.authenticate` in `@beezping/server`): without it the browser
339
- * never attaches the cookie and every request is rejected as
340
- * unauthenticated. The server must allow the page's origin explicitly with
341
- * credentialed CORS (`allowedOrigins`) — a wildcard origin never works
342
- * with cookies. Only opt in for origins you trust: cookie-authenticated
343
- * cross-origin requests rely on the server's CSRF defenses (strict origin
344
- * allowlist, `SameSite` cookies).
345
- *
346
- * An unknown value (untyped script-tag config) is rejected at init: the
347
- * widget logs an error and does not load.
348
- */
349
- credentials?: SitepingRequestCredentials | undefined;
415
+ * token). Merged over the widget's generated headers, case-insensitively,
416
+ * so an explicit `Authorization` entry overrides `apiKey`. A factory that
417
+ * throws, rejects, or does not settle within 10 s fails the request like a
418
+ * network error.
419
+ */
420
+ headers?: BeezpingHeadersOption | undefined;
350
421
  /** Not available in HTTP mode — use either `endpoint` or `store`, never both. */
351
422
  store?: never;
352
423
  }
353
424
  /**
354
- * Store mode — the widget talks to a `SitepingStore` directly in the
425
+ * Store mode — the widget talks to a `BeezpingStore` directly in the
355
426
  * browser, no server needed (demos, prototypes, localStorage persistence).
356
427
  */
357
- export interface SitepingStoreConfig extends SitepingBaseConfig {
358
- /** Direct store for client-side mode. Bypasses HTTP entirely. */
359
- store: SitepingStore;
428
+ export interface BeezpingStoreConfig extends BeezpingBaseConfig {
429
+ /**
430
+ * Direct store for client-side mode. Bypasses HTTP entirely. A send stops
431
+ * waiting on `createFeedback` after 30 s (the call itself cannot be
432
+ * cancelled), so a network-backed store should bound its own calls.
433
+ */
434
+ store: BeezpingStore;
360
435
  /** Not available in store mode — use either `endpoint` or `store`, never both. */
361
436
  endpoint?: never;
362
437
  /** HTTP-mode only — meaningless without an `endpoint`. */
363
438
  apiKey?: never;
364
439
  /** HTTP-mode only — meaningless without an `endpoint`. */
365
440
  headers?: never;
366
- /** HTTP-mode only — meaningless without an `endpoint`. */
367
- credentials?: never;
368
441
  }
369
442
  /**
370
- * Configuration options for the Siteping widget.
443
+ * Configuration options for the Beezping widget.
371
444
  *
372
445
  * A discriminated union over the two transport modes: pass `endpoint`
373
- * (HTTP mode, optionally with `apiKey`/`headers`/`credentials`) **or** `store` (direct
446
+ * (HTTP mode, optionally with `apiKey`/`headers`) **or** `store` (direct
374
447
  * client-side mode) — never both, never neither. Invalid combinations are
375
448
  * compile errors instead of runtime warnings.
376
449
  */
377
- export type SitepingConfig = SitepingHttpConfig | SitepingStoreConfig;
378
- /** Instance returned by initSiteping() with lifecycle methods. */
379
- export interface SitepingInstance {
450
+ export type BeezpingConfig = BeezpingHttpConfig | BeezpingStoreConfig;
451
+ /** Instance returned by initBeezping() with lifecycle methods. */
452
+ export interface BeezpingInstance {
380
453
  /** Remove the widget from the DOM and clean up all listeners. */
381
454
  destroy: () => void;
382
455
  /** Open the panel programmatically */
@@ -397,21 +470,23 @@ export interface SitepingInstance {
397
470
  */
398
471
  focusFeedback: (feedbackId: string) => boolean;
399
472
  /** Subscribe to a public widget event */
400
- on: <K extends keyof SitepingPublicEvents>(event: K, listener: SitepingPublicEventListener<K>) => SitepingUnsubscribe;
473
+ on: <K extends keyof BeezpingPublicEvents>(event: K, listener: BeezpingPublicEventListener<K>) => BeezpingUnsubscribe;
401
474
  /** Unsubscribe from a public widget event */
402
- off: <K extends keyof SitepingPublicEvents>(event: K, listener: SitepingPublicEventListener<K>) => void;
403
- }
404
- /** Listener signature for a single `SitepingPublicEvents` key. */
405
- export type SitepingPublicEventListener<K extends keyof SitepingPublicEvents> = (...args: SitepingPublicEvents[K]) => void;
406
- /** Disposer returned by `SitepingInstance.on` — call once to detach the listener. */
407
- export type SitepingUnsubscribe = () => void;
408
- /** Events exposed to consumers via SitepingInstance.on / .off */
409
- export interface SitepingPublicEvents {
475
+ off: <K extends keyof BeezpingPublicEvents>(event: K, listener: BeezpingPublicEventListener<K>) => void;
476
+ }
477
+ /** Listener signature for a single `BeezpingPublicEvents` key. */
478
+ export type BeezpingPublicEventListener<K extends keyof BeezpingPublicEvents> = (...args: BeezpingPublicEvents[K]) => void;
479
+ /** Disposer returned by `BeezpingInstance.on` — call once to detach the listener. */
480
+ export type BeezpingUnsubscribe = () => void;
481
+ /** Events exposed to consumers via BeezpingInstance.on / .off */
482
+ export interface BeezpingPublicEvents {
410
483
  "feedback:sent": [FeedbackResponse];
411
484
  "feedback:deleted": [FeedbackResponse["id"]];
485
+ /** A reply was posted from the panel's discussion thread. */
486
+ "comment:added": [CommentResponse];
412
487
  /**
413
488
  * A feedback API call failed. Same payload contract as
414
- * `SitepingConfig.onError` — a `SitepingError` subclass in HTTP mode,
489
+ * `BeezpingConfig.onError` — a `BeezpingError` subclass in HTTP mode,
415
490
  * possibly a raw `Error` in store mode.
416
491
  */
417
492
  "feedback:error": [Error];
@@ -444,7 +519,7 @@ export type OpenFeedbackStatus = (typeof OPEN_FEEDBACK_STATUSES)[number];
444
519
  /** Whether a status is terminal (`resolved` or `wont_fix`). Narrows the status type. */
445
520
  export declare function isClosedStatus(status: FeedbackStatus): status is ClosedFeedbackStatus;
446
521
  /**
447
- * Page scope returned by `SitepingConfig.getPageScope()`.
522
+ * Page scope returned by `BeezpingConfig.getPageScope()`.
448
523
  *
449
524
  * - `url`: concrete page identifier — usually `window.location.pathname`,
450
525
  * used as the strict scope for marker rendering.
@@ -495,7 +570,7 @@ export interface FeedbackCreateInput {
495
570
  screenshotRegion?: ScreenshotRegion | null | undefined;
496
571
  /**
497
572
  * Optional console + failed-network snapshot captured by the widget when
498
- * `SitepingConfig.captureDiagnostics` is enabled. Stored as JSON on
573
+ * `BeezpingConfig.captureDiagnostics` is enabled. Stored as JSON on
499
574
  * `FeedbackRecord.diagnostics` so reviewers can replay the context.
500
575
  */
501
576
  diagnostics?: DiagnosticsSnapshot | null | undefined;
@@ -543,6 +618,11 @@ export interface FeedbackQuery {
543
618
  statuses?: readonly FeedbackStatus[] | undefined;
544
619
  search?: string | undefined;
545
620
  page?: number | undefined;
621
+ /**
622
+ * Page size. Defaults to `DEFAULT_PAGE_LIMIT` (50) and is capped at
623
+ * `MAX_PAGE_LIMIT` (100) — `clampPagination` implements both, and the
624
+ * conformance suite checks them.
625
+ */
546
626
  limit?: number | undefined;
547
627
  /**
548
628
  * Filter to feedbacks created on this exact URL (path). Used by the panel's
@@ -601,6 +681,13 @@ export interface FeedbackRecord {
601
681
  createdAt: Date;
602
682
  updatedAt: Date;
603
683
  annotations: AnnotationRecord[];
684
+ /**
685
+ * Discussion thread, oldest first. A store that implements
686
+ * `BeezpingStore.addComment` returns it on every record; a store without
687
+ * comments may leave it out, which reads as an empty thread (HTTP handlers
688
+ * send `[]`).
689
+ */
690
+ comments?: CommentRecord[] | undefined;
604
691
  /**
605
692
  * URL the widget renders as `<img src>`. Either an `https://...` from a
606
693
  * configured `ScreenshotStorage`, or a `data:image/jpeg;base64,...` URL
@@ -648,6 +735,66 @@ export interface AnnotationRecord {
648
735
  devicePixelRatio: number;
649
736
  createdAt: Date;
650
737
  }
738
+ /**
739
+ * Who wrote a comment: `client` — the reviewer on the site (the widget) —
740
+ * or `team` — the project side (the dashboard). HTTP handlers stamp
741
+ * `client` unless the server's access policy vouches for the caller, so a
742
+ * visitor cannot pose as the team.
743
+ */
744
+ export declare const COMMENT_AUTHOR_ROLES: readonly ["client", "team"];
745
+ export type CommentAuthorRole = (typeof COMMENT_AUTHOR_ROLES)[number];
746
+ /** Longest comment `body` the HTTP API accepts — the cap of a feedback `message`. */
747
+ export declare const COMMENT_BODY_MAX_LENGTH = 5000;
748
+ /**
749
+ * Most `client` comments one thread holds. List responses embed whole
750
+ * threads, which are not paginated, so this bounds what one feedback weighs
751
+ * however much a public endpoint is spammed. `team` comments — a role only
752
+ * the access policy grants — neither count nor meet it: a thread spammed
753
+ * full still takes the team's answer. Stores enforce it with
754
+ * `StoreLimitError`; a store without an atomic primitive may overshoot it by
755
+ * the posts that race the last free slot.
756
+ */
757
+ export declare const MAX_COMMENTS_PER_FEEDBACK = 100;
758
+ /** A persisted comment returned by the store. */
759
+ export interface CommentRecord {
760
+ id: string;
761
+ /** The feedback whose thread holds this comment. */
762
+ feedbackId: string;
763
+ body: string;
764
+ authorName: string;
765
+ /**
766
+ * Author email, or `""` when the author has none on file (a dashboard
767
+ * user). HTTP handlers redact it exactly like `FeedbackRecord.authorEmail`.
768
+ */
769
+ authorEmail: string;
770
+ authorRole: CommentAuthorRole;
771
+ /** Client-generated id — `addComment` is idempotent on it, so a retried post never duplicates a reply. */
772
+ clientId: string;
773
+ createdAt: Date;
774
+ }
775
+ /** Input of `BeezpingStore.addComment`. */
776
+ export interface CommentCreateInput {
777
+ body: string;
778
+ authorName: string;
779
+ authorEmail: string;
780
+ authorRole: CommentAuthorRole;
781
+ clientId: string;
782
+ }
783
+ /**
784
+ * Body of a comment `POST`. `feedbackId` is what tells it apart from a
785
+ * feedback submission on the same endpoint; `projectName` scopes the write
786
+ * to that project's feedbacks.
787
+ */
788
+ export interface CommentPayload extends CommentCreateInput {
789
+ projectName: string;
790
+ feedbackId: string;
791
+ }
792
+ /** Body of a comment `DELETE` — `commentId` is what tells it apart from a feedback delete. */
793
+ export interface CommentDeletePayload {
794
+ projectName: string;
795
+ feedbackId: string;
796
+ commentId: string;
797
+ }
651
798
  /**
652
799
  * Thrown when a record is not found during update or delete.
653
800
  *
@@ -677,24 +824,41 @@ export declare class StorePersistenceError extends Error {
677
824
  readonly code: "STORE_PERSISTENCE";
678
825
  constructor(message?: string, options?: ErrorOptions);
679
826
  }
827
+ /**
828
+ * Thrown when a write would break a bound of the store contract — a
829
+ * `client` comment on a thread already holding `MAX_COMMENTS_PER_FEEDBACK`
830
+ * of them. Handlers translate this to HTTP 409.
831
+ */
832
+ export declare class StoreLimitError extends Error {
833
+ readonly code: "STORE_LIMIT";
834
+ constructor(message?: string, options?: ErrorOptions);
835
+ }
836
+ /**
837
+ * Thrown when a value is longer than the store can hold, though the HTTP
838
+ * validation accepts it — on MySQL, Prisma maps a plain `String` to
839
+ * `VARCHAR(191)`. Handlers translate this to HTTP 422: the submission can
840
+ * never be stored as it is, so a client must not retry it.
841
+ */
842
+ export declare class StoreValueTooLongError extends Error {
843
+ readonly code: "STORE_VALUE_TOO_LONG";
844
+ constructor(message?: string, options?: ErrorOptions);
845
+ }
680
846
  /** Shape of any ORM error that carries a Prisma-style `code` field. */
681
847
  type CodedError<C extends string = string> = {
682
848
  code: C;
683
849
  };
684
850
  /**
685
851
  * Type guard — works for `StoreNotFoundError` and ORM-specific equivalents
686
- * (e.g. Prisma P2025). Matches the stable `STORE_NOT_FOUND` code as well as
687
- * `instanceof`: every consumer package bundles its own copy of core (tsup
688
- * `noExternal`), so an adapter's instance fails `instanceof` against the
689
- * server's class identity.
852
+ * (e.g. Prisma P2025). Matches on the stable `code` too, for the same
853
+ * cross-bundle reason as {@link isStorePersistence}.
690
854
  */
691
- export declare function isStoreNotFound(error: unknown): error is StoreNotFoundError | CodedError<"STORE_NOT_FOUND"> | CodedError<"P2025">;
855
+ export declare function isStoreNotFound(error: unknown): error is StoreNotFoundError | CodedError<"STORE_NOT_FOUND" | "P2025">;
692
856
  /**
693
857
  * Type guard — works for `StoreDuplicateError` and ORM-specific equivalents
694
- * (e.g. Prisma P2002). Matches the stable `STORE_DUPLICATE` code as well as
695
- * `instanceof`, for the same cross-bundle reason as {@link isStoreNotFound}.
858
+ * (e.g. Prisma P2002). Matches on the stable `code` too, for the same
859
+ * cross-bundle reason as {@link isStorePersistence}.
696
860
  */
697
- export declare function isStoreDuplicate(error: unknown): error is StoreDuplicateError | CodedError<"STORE_DUPLICATE"> | CodedError<"P2002">;
861
+ export declare function isStoreDuplicate(error: unknown): error is StoreDuplicateError | CodedError<"STORE_DUPLICATE" | "P2002">;
698
862
  /**
699
863
  * Type guard for `StorePersistenceError`. Matches on the stable `code` field
700
864
  * in addition to `instanceof`: every consumer package bundles its own copy of
@@ -702,10 +866,14 @@ export declare function isStoreDuplicate(error: unknown): error is StoreDuplicat
702
866
  * `instanceof` check against another package's class identity.
703
867
  */
704
868
  export declare function isStorePersistence(error: unknown): error is StorePersistenceError | CodedError<"STORE_PERSISTENCE">;
869
+ /** Type guard for `StoreLimitError`, matching on `code` for the same cross-bundle reason as {@link isStorePersistence}. */
870
+ export declare function isStoreLimit(error: unknown): error is StoreLimitError | CodedError<"STORE_LIMIT">;
871
+ /** Type guard for `StoreValueTooLongError`, matching on `code` for the same cross-bundle reason as {@link isStorePersistence}. */
872
+ export declare function isStoreValueTooLong(error: unknown): error is StoreValueTooLongError | CodedError<"STORE_VALUE_TOO_LONG">;
705
873
  /** Flatten a widget `AnnotationPayload` (nested anchor + rect) into a flat `AnnotationCreateInput`. */
706
874
  export declare function flattenAnnotation(ann: AnnotationPayload): AnnotationCreateInput;
707
875
  /**
708
- * Outcome of `SitepingStore.createFeedbackIfAbsent` — the record plus whether
876
+ * Outcome of `BeezpingStore.createFeedbackIfAbsent` — the record plus whether
709
877
  * this very call inserted it.
710
878
  */
711
879
  export interface FeedbackCreateOutcome {
@@ -717,17 +885,17 @@ export interface FeedbackCreateOutcome {
717
885
  */
718
886
  created: boolean;
719
887
  }
720
- /** Paginated result returned by `SitepingStore.getFeedbacks`. */
888
+ /** Paginated result returned by `BeezpingStore.getFeedbacks`. */
721
889
  export interface FeedbackPage {
722
890
  feedbacks: FeedbackRecord[];
723
891
  total: number;
724
892
  }
725
893
  /**
726
- * Abstract storage interface for Siteping.
894
+ * Abstract storage interface for Beezping.
727
895
  *
728
896
  * Any adapter (Prisma, Drizzle, raw SQL, localStorage, etc.) implements this
729
897
  * interface. The HTTP handler and widget `StoreClient` operate against
730
- * `SitepingStore`, decoupled from the storage backend.
898
+ * `BeezpingStore`, decoupled from the storage backend.
731
899
  *
732
900
  * ## Error contract
733
901
  *
@@ -744,7 +912,7 @@ export interface FeedbackPage {
744
912
  * a phantom success. Detect it with `isStorePersistence`.
745
913
  * - Other methods should not throw on empty results — return empty arrays or `null`.
746
914
  */
747
- export interface SitepingStore {
915
+ export interface BeezpingStore {
748
916
  /** Create a feedback with its annotations. Idempotent on `clientId` — return existing record on duplicate, or throw `StoreDuplicateError`. Throws `StorePersistenceError` when the write cannot be persisted. */
749
917
  createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;
750
918
  /** Paginated query with optional filters. Returns empty array (not error) when no results. */
@@ -762,10 +930,8 @@ export interface SitepingStore {
762
930
  * `projectName`, `false` otherwise (including when it does not exist).
763
931
  *
764
932
  * HTTP handlers use this to reject cross-project PATCH/DELETE requests.
765
- * Implement it whenever your store serves multiple projects. When absent,
766
- * handlers rely on `id` alone — so `createSitepingHandler` refuses to start
767
- * with a custom `access.authorize` (which may scope callers to projects)
768
- * over a store that lacks it.
933
+ * Implement it whenever your store serves multiple projects; when absent,
934
+ * handlers skip the ownership check and rely on `id` alone.
769
935
  */
770
936
  verifyProjectOwnership?(id: string, projectName: string): Promise<boolean>;
771
937
  /**
@@ -780,12 +946,39 @@ export interface SitepingStore {
780
946
  * transaction, compare-and-set).
781
947
  *
782
948
  * HTTP handlers prefer it over `createFeedback` to fire creation side
783
- * effects (webhooks, `onCreated`) exactly once when concurrent requests
784
- * race on the same `clientId`. Stores whose `createFeedback` throws
949
+ * effects (webhooks) exactly once when concurrent requests race on the
950
+ * same `clientId`. Stores whose `createFeedback` throws
785
951
  * `StoreDuplicateError` on a duplicate already give that signal and may
786
952
  * leave it out.
787
953
  */
788
954
  createFeedbackIfAbsent?(data: FeedbackCreateInput): Promise<FeedbackCreateOutcome>;
955
+ /**
956
+ * Optional — append a comment to the thread of feedback `feedbackId` and
957
+ * return it. Idempotent on `data.clientId`: when a comment with that
958
+ * `clientId` exists, on any thread, return it unchanged (HTTP handlers
959
+ * refuse a replay aimed at another thread).
960
+ *
961
+ * Throws `StoreNotFoundError` when the feedback does not exist,
962
+ * `StoreLimitError` when a `client` comment meets a thread already holding
963
+ * `MAX_COMMENTS_PER_FEEDBACK` of them (a `team` comment is never refused
964
+ * for it), `StorePersistenceError` when the
965
+ * write cannot be persisted. A comment is not a change of the feedback
966
+ * itself: its `updatedAt` stays as it was.
967
+ *
968
+ * A store that implements it returns every feedback's thread on
969
+ * `FeedbackRecord.comments`, oldest first, and deletes a thread with its
970
+ * feedback. A store without it (and `deleteComment`) has no threads:
971
+ * HTTP handlers serve empty threads and answer comment writes with 501.
972
+ */
973
+ addComment?(feedbackId: string, data: CommentCreateInput): Promise<CommentRecord>;
974
+ /**
975
+ * Optional — delete comment `commentId` from the thread of `feedbackId`.
976
+ * Scoped to that thread, which the caller was authorized for: throws
977
+ * `StoreNotFoundError` when the feedback does not exist or the comment is
978
+ * not on its thread, `StorePersistenceError` when the write cannot be
979
+ * persisted.
980
+ */
981
+ deleteComment?(feedbackId: string, commentId: string): Promise<void>;
789
982
  }
790
983
  /** Payload sent from the widget to the server when submitting feedback. */
791
984
  export interface FeedbackPayload {
@@ -795,7 +988,7 @@ export interface FeedbackPayload {
795
988
  url: string;
796
989
  /**
797
990
  * Parameterized URL template (e.g. `/orders/:orderId`) supplied by
798
- * `SitepingConfig.getPageScope()`. Null when the host did not provide one.
991
+ * `BeezpingConfig.getPageScope()`. Null when the host did not provide one.
799
992
  */
800
993
  urlPattern?: string | null | undefined;
801
994
  viewport: string;
@@ -807,7 +1000,7 @@ export interface FeedbackPayload {
807
1000
  clientId: string;
808
1001
  /**
809
1002
  * Base64 JPEG `data:` URL of the annotated area. Captured by the widget
810
- * when `enableScreenshot: true` is set in `SitepingConfig`. Null when
1003
+ * when `enableScreenshot: true` is set in `BeezpingConfig`. Null when
811
1004
  * disabled or when capture failed silently.
812
1005
  */
813
1006
  screenshotDataUrl?: string | null | undefined;
@@ -857,11 +1050,26 @@ export interface DiagnosticsSnapshot {
857
1050
  console: ConsoleDiagnosticEntry[];
858
1051
  network: NetworkDiagnosticEntry[];
859
1052
  }
1053
+ /**
1054
+ * Length caps for `AnchorData.elementTag` / `elementId`, shared by the widget
1055
+ * (which bounds what it captures) and the HTTP adapter (which rejects a longer
1056
+ * tag and drops a longer id) so the two can't drift. 191 fits Prisma's default `String` column
1057
+ * on MySQL (`VARCHAR(191)`); real tag names and ids are far shorter.
1058
+ */
1059
+ export declare const ANCHOR_ELEMENT_TAG_MAX = 191;
1060
+ export declare const ANCHOR_ELEMENT_ID_MAX = 191;
860
1061
  /** DOM anchoring data for re-attaching annotations to page elements. */
861
1062
  export interface AnchorData {
862
- /** CSS selector generated by @medv/finder — primary anchor */
1063
+ /**
1064
+ * CSS selector generated by @medv/finder — primary anchor. Inside open
1065
+ * shadow roots: one selector per tree, outermost host first, joined by
1066
+ * `" >>> "` (e.g. `"#pricing >>> .plan-title"`).
1067
+ */
863
1068
  cssSelector: string;
864
- /** XPath — fallback 1 */
1069
+ /**
1070
+ * XPath — fallback 1. Inside a shadow root it is relative to that root
1071
+ * (`./…`) and informational only: XPath cannot enter shadow trees.
1072
+ */
865
1073
  xpath: string;
866
1074
  /** First ~120 chars of element innerText — empty string if none */
867
1075
  textSnippet: string;
@@ -926,22 +1134,73 @@ export interface AnnotationPayload {
926
1134
  /**
927
1135
  * Feedback record as returned by the API — derived from
928
1136
  * {@link FeedbackRecord}: dates are serialized to ISO strings and `clientId`
929
- * is omitted (server-side dedup concern, never exposed on the wire). Adding
930
- * a field to `FeedbackRecord` updates this type automatically.
1137
+ * is omitted (server-side dedup concern, never exposed on the wire), on the
1138
+ * record and on each comment of its thread. Adding a field to
1139
+ * `FeedbackRecord` updates this type automatically.
931
1140
  *
932
1141
  * Note: `authorEmail` may be an empty string — HTTP adapters redact it for
933
1142
  * unauthenticated requests; the full value requires a Bearer-authenticated
934
1143
  * request.
935
1144
  */
936
- export type FeedbackResponse = Prettify<Serialized<Omit<FeedbackRecord, "clientId">>>;
1145
+ export type FeedbackResponse = Prettify<Serialized<Omit<FeedbackRecord, "clientId" | "comments">> & {
1146
+ /** The thread, oldest first — always sent by `@beezping/server`, absent from servers that predate comments. */
1147
+ comments?: CommentResponse[] | undefined;
1148
+ /**
1149
+ * What the requester may do with this feedback — always sent by
1150
+ * `@beezping/server`. Absent from servers that predate it, and in store
1151
+ * mode: nothing is refused then.
1152
+ */
1153
+ permissions?: FeedbackPermissions | undefined;
1154
+ }>;
1155
+ /**
1156
+ * What a requester may do with one feedback, as the server's access policy
1157
+ * decides — so clients hide the actions it would refuse. The server still
1158
+ * enforces every one of them.
1159
+ */
1160
+ export interface FeedbackPermissions {
1161
+ /** Change its status: resolve, reopen, … */
1162
+ canChangeStatus: boolean;
1163
+ /** Delete it. */
1164
+ canDelete: boolean;
1165
+ /** Reply in its thread. */
1166
+ canComment: boolean;
1167
+ /** Delete replies from its thread. */
1168
+ canDeleteComment: boolean;
1169
+ }
1170
+ /** What a requester may do with a whole project, sent with each list. */
1171
+ export interface FeedbackListPermissions {
1172
+ /** Delete every feedback of the project at once. */
1173
+ canDeleteAll: boolean;
1174
+ }
937
1175
  /**
938
1176
  * Annotation record as returned by the API — {@link AnnotationRecord} with
939
1177
  * `createdAt` serialized to an ISO string.
940
1178
  */
941
1179
  export type AnnotationResponse = Prettify<Serialized<AnnotationRecord>>;
1180
+ /**
1181
+ * Comment as returned by the API — {@link CommentRecord} with `createdAt`
1182
+ * serialized and `clientId` omitted, like on `FeedbackResponse`. Its
1183
+ * `authorEmail` is redacted on the same terms as the feedback's.
1184
+ */
1185
+ export type CommentResponse = Prettify<Serialized<Omit<CommentRecord, "clientId">>>;
1186
+ /** What the store behind an endpoint supports — advertised on every list response. */
1187
+ export interface BeezpingCapabilities {
1188
+ /** Whether comments can be posted: the store implements `addComment`. */
1189
+ comments: boolean;
1190
+ /**
1191
+ * Whether comments can be deleted: the store implements `deleteComment`.
1192
+ * Sent by `@beezping/server`; a client that does not delete (the widget)
1193
+ * leaves it out.
1194
+ */
1195
+ deleteComments?: boolean | undefined;
1196
+ }
942
1197
  /** Paginated `FeedbackResponse` shape returned by the API. */
943
1198
  export interface FeedbackResponseList {
944
1199
  feedbacks: FeedbackResponse[];
945
1200
  total: number;
1201
+ /** Always sent by `@beezping/server` — absent from servers that predate it. */
1202
+ capabilities?: BeezpingCapabilities | undefined;
1203
+ /** Always sent by `@beezping/server` — absent from servers that predate it. */
1204
+ permissions?: FeedbackListPermissions | undefined;
946
1205
  }
947
1206
  export {};