@verbumia/feedback 0.2.6 → 0.2.7

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.
@@ -81,11 +81,61 @@ var FeedbackClient = class {
81
81
  get hasConsented() {
82
82
  return this.tokens !== null;
83
83
  }
84
+ /** Alias of `hasConsented` exposed under the 0.2.7 naming used by the
85
+ * `controller.hasAcceptedTos` getter (the SDK's built-in modal and a
86
+ * host's external ToS page share the same persisted token bundle, so
87
+ * both flip this boolean once a session is bootstrapped). */
88
+ get hasAcceptedTos() {
89
+ return this.tokens !== null;
90
+ }
84
91
  /** Server-minted sessionId / grouping_key (from the token bundle).
85
92
  * Available only after `acceptTos()`; never client-generated. */
86
93
  get sessionId() {
87
94
  return this.tokens?.grouping_key;
88
95
  }
96
+ /**
97
+ * 0.2.7 — `GET /v1/projects/{projectId}/feedback-addon/state`.
98
+ *
99
+ * Auth selection (matches backend's dual-acceptance after task 836's
100
+ * follow-up PR — see CONTRACT note in the response type):
101
+ * - If `cfg.apiKey` is set → `Authorization: ApiKey <key>` (so the
102
+ * plugin can fetch state at `setup()` time, before the user has
103
+ * accepted ToS).
104
+ * - Else if a user-session bundle exists → `Authorization: Bearer
105
+ * <access_token>` (the deferred-after-acceptTos path).
106
+ * - Else → throw `FeedbackError("addon state requires apiKey or
107
+ * acceptTos")`. The plugin's setup catches and surfaces a
108
+ * console.warn the first time, then re-attempts on the
109
+ * bundle-mint that follows `acceptTos()`.
110
+ *
111
+ * Best-effort: a transport error returns `null` instead of throwing,
112
+ * so the controller's `isActive` falls back to whatever was last
113
+ * known (or its initial value) without flipping the host's CTA into
114
+ * an inconsistent state on a transient blip.
115
+ */
116
+ async getAddonState() {
117
+ let auth;
118
+ if (this.cfg.apiKey) {
119
+ auth = `ApiKey ${this.cfg.apiKey}`;
120
+ } else if (this.tokens) {
121
+ auth = `Bearer ${this.tokens.access_token}`;
122
+ } else {
123
+ throw new FeedbackError(
124
+ "addon state requires apiKey or acceptTos"
125
+ );
126
+ }
127
+ const url = `${this.base()}/v1/projects/${this.cfg.projectId}/feedback-addon/state`;
128
+ try {
129
+ const res = await this.fetchImpl(url, {
130
+ method: "GET",
131
+ headers: { Authorization: auth }
132
+ });
133
+ if (!res.ok) return null;
134
+ return await res.json();
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
89
139
  /** BCP-47 language the widget is rating strings in. */
90
140
  get language() {
91
141
  return this.cfg.language;
@@ -157,9 +207,18 @@ var FeedbackClient = class {
157
207
  }
158
208
  this.tokens = await res.json();
159
209
  }
160
- /** Authenticated fetch with a single transparent refresh-on-401 retry. */
210
+ /** Authenticated fetch with a single transparent refresh-on-401 retry.
211
+ * When `cfg.autoAcceptTos === false` (the plugin's `tos: "skip"` opt-in
212
+ * / 0.2.7) and no token has been minted yet, throws a `not consented`
213
+ * error instead of silently calling `acceptTos()` — the host promises
214
+ * to handle consent externally via `controller.acceptTos()`. */
161
215
  async authed(path, init, retry = true) {
162
- if (!this.tokens) await this.acceptTos();
216
+ if (!this.tokens) {
217
+ if (this.cfg.autoAcceptTos === false) {
218
+ throw new FeedbackError("not consented");
219
+ }
220
+ await this.acceptTos();
221
+ }
163
222
  const res = await this.fetchImpl(`${this.base()}${path}`, {
164
223
  ...init,
165
224
  headers: {
@@ -303,8 +362,10 @@ var C = {
303
362
  emeraldSoft: "#34d399"
304
363
  };
305
364
  function FeedbackPanel(props) {
306
- const { client, keys, namespace, onClose } = props;
307
- const [consented, setConsented] = (0, import_react.useState)(client.hasConsented);
365
+ const { client, keys, namespace, tos = "modal", onClose } = props;
366
+ const [consented, setConsented] = (0, import_react.useState)(
367
+ tos === "skip" ? true : client.hasConsented
368
+ );
308
369
  const [busy, setBusy] = (0, import_react.useState)(false);
309
370
  const [error, setError] = (0, import_react.useState)(null);
310
371
  const [strings, setStrings] = (0, import_react.useState)([]);
@@ -590,42 +651,73 @@ function StringRow(props) {
590
651
  // src/react/plugin.tsx
591
652
  var import_jsx_runtime2 = require("react/jsx-runtime");
592
653
  function makeStore() {
593
- let open = false;
654
+ let state = { isOpen: false, snapshotKeys: void 0 };
594
655
  const listeners = /* @__PURE__ */ new Set();
595
656
  return {
596
- isOpen: () => open,
597
- set(v) {
598
- if (open !== v) {
599
- open = v;
600
- listeners.forEach((l) => l());
657
+ getState: () => state,
658
+ setOpen(open, snapshotKeys) {
659
+ const next = {
660
+ isOpen: open,
661
+ snapshotKeys: open ? snapshotKeys : void 0
662
+ };
663
+ if (state.isOpen === next.isOpen && state.snapshotKeys === next.snapshotKeys) {
664
+ return;
601
665
  }
666
+ state = next;
667
+ listeners.forEach((l) => l());
602
668
  },
603
669
  subscribe(l) {
604
670
  listeners.add(l);
605
- return () => listeners.delete(l);
671
+ return () => {
672
+ listeners.delete(l);
673
+ };
606
674
  }
607
675
  };
608
676
  }
677
+ async function captureCurrentViewSnapshot(i18next) {
678
+ const reg = globalThis.__verbumia_key_registry__;
679
+ if (!reg) return [];
680
+ const lng = i18next?.language;
681
+ const change = i18next?.changeLanguage;
682
+ if (typeof change === "function" && typeof lng === "string" && lng) {
683
+ reg.reset?.();
684
+ try {
685
+ await change(lng);
686
+ } catch {
687
+ }
688
+ await new Promise((r) => {
689
+ if (typeof requestAnimationFrame === "function") {
690
+ requestAnimationFrame(() => r());
691
+ } else {
692
+ setTimeout(() => r(), 16);
693
+ }
694
+ });
695
+ return reg.snapshot();
696
+ }
697
+ return reg.snapshot();
698
+ }
609
699
  function feedbackPlugin(options) {
610
700
  const store = makeStore();
611
701
  let client = null;
612
702
  function Outlet() {
613
- const isOpen = (0, import_react2.useSyncExternalStore)(
703
+ const state = (0, import_react2.useSyncExternalStore)(
614
704
  store.subscribe,
615
- store.isOpen,
616
- store.isOpen
705
+ store.getState,
706
+ store.getState
617
707
  );
618
- if (!isOpen || !client || typeof document === "undefined") return null;
708
+ if (!state.isOpen || !client || typeof document === "undefined") return null;
619
709
  const c = client;
710
+ const panelKeys = options.keys ?? state.snapshotKeys;
620
711
  return (0, import_react_dom.createPortal)(
621
712
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
622
713
  FeedbackPanel,
623
714
  {
624
715
  client: c,
625
- keys: options.keys,
716
+ keys: panelKeys,
626
717
  namespace: options.namespace,
718
+ tos: options.tos ?? "modal",
627
719
  onClose: () => {
628
- store.set(false);
720
+ store.setOpen(false);
629
721
  void c.flush();
630
722
  }
631
723
  }
@@ -637,24 +729,71 @@ function feedbackPlugin(options) {
637
729
  name: "@verbumia/feedback",
638
730
  setup(ctx) {
639
731
  const initialLanguage = options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;
732
+ const tos = options.tos ?? "modal";
640
733
  client = new FeedbackClient({
641
734
  apiBase: options.apiBase ?? ctx.config.apiBase ?? "https://api.verbumia.dev",
642
735
  projectId: options.projectId ?? ctx.config.projectUuid,
643
736
  language: initialLanguage,
644
737
  endUserId: options.endUserId,
645
- fetchImpl: options.fetchImpl
738
+ fetchImpl: options.fetchImpl,
739
+ apiKey: options.apiKey,
740
+ autoAcceptTos: tos !== "skip"
646
741
  });
742
+ const clientRef = client;
743
+ let addonState = null;
744
+ const cta = options.cta ?? "auto";
745
+ const scope = options.scope ?? "current-view";
746
+ const ctxI18next = ctx.i18n?.i18next;
747
+ const refreshState = async () => {
748
+ try {
749
+ const next = await clientRef.getAddonState();
750
+ if (next !== null) addonState = next;
751
+ } catch {
752
+ }
753
+ };
754
+ if (options.apiKey) void refreshState();
647
755
  let langUnsub;
648
756
  if (typeof ctx.onLanguageChange === "function") {
649
- langUnsub = ctx.onLanguageChange((lng) => client?.setLanguage(lng));
757
+ langUnsub = ctx.onLanguageChange((lng) => {
758
+ clientRef.setLanguage(lng);
759
+ void refreshState();
760
+ });
650
761
  }
651
762
  const controller = {
652
- open: () => store.set(true),
763
+ open: async () => {
764
+ if (scope === "current-view") {
765
+ const snapshot = await captureCurrentViewSnapshot(ctxI18next);
766
+ store.setOpen(true, snapshot);
767
+ return;
768
+ }
769
+ store.setOpen(true);
770
+ },
653
771
  close: () => {
654
- store.set(false);
655
- void client?.flush();
772
+ store.setOpen(false);
773
+ void clientRef?.flush();
774
+ },
775
+ client: clientRef,
776
+ get tosVersion() {
777
+ return clientRef.tosVersion;
778
+ },
779
+ get hasAcceptedTos() {
780
+ return clientRef.hasAcceptedTos;
656
781
  },
657
- client
782
+ acceptTos: async () => {
783
+ await clientRef.acceptTos();
784
+ if (!options.apiKey) await refreshState();
785
+ },
786
+ get isActive() {
787
+ if (cta === "show") return true;
788
+ if (cta === "hide") return false;
789
+ return addonState ? addonState.isActive : null;
790
+ },
791
+ get enabledLanguages() {
792
+ return addonState ? addonState.enabledLanguages : null;
793
+ },
794
+ get sku() {
795
+ return addonState ? addonState.sku : null;
796
+ }
658
797
  };
659
798
  options.onReady?.(controller);
660
799
  if (options.controllerRef) options.controllerRef.current = controller;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts","../../src/react/plugin.tsx","../../src/core/types.ts","../../src/core/tos.ts","../../src/core/client.ts","../../src/react/panel.tsx","../../src/core/keys.ts"],"sourcesContent":["export {\n feedbackPlugin,\n type FeedbackController,\n type FeedbackPluginOptions,\n type I18nPlugin,\n type I18nPluginContext,\n} from \"./plugin\";\nexport { FeedbackPanel } from \"./panel\";\nexport {\n FeedbackClient,\n hasKeyRegistry,\n resolveKeys,\n type BatchResponse,\n type DeclaredKey,\n type FeedbackConfig,\n FeedbackError,\n type FeedbackString,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"../core\";\n","/**\n * `@verbumia/feedback` as a PLUGIN of the `@verbumia/*-i18n` provider.\n *\n * Architecture (task 599): NO second React context. The customer adds\n * `feedbackPlugin(...)` to the i18n provider's `plugins` slot. The\n * provider calls `setup({ i18n, config })` once (we reuse its apiBase /\n * projectUuid / locale — no re-config) and renders our `render()` as an\n * ISOLATED sibling leaf. The panel's open/close lives in a tiny private\n * store the outlet subscribes to via useSyncExternalStore, so toggling\n * feedback NEVER re-renders the host app. The customer triggers it via\n * their own CTA through the imperative controller.\n */\nimport { createPortal } from \"react-dom\";\nimport { useSyncExternalStore, type ReactNode } from \"react\";\n\nimport { FeedbackClient } from \"../core/client\";\nimport type { DeclaredKey } from \"../core/types\";\nimport { FeedbackPanel } from \"./panel\";\n\n/** Structural mirror of `@verbumia/react-i18next`'s VerbumiaPlugin —\n * kept local so feedback has no build-time dep on the i18n SDK. The\n * `i18n` and `onLanguageChange` fields are optional so a plugin attached\n * to a non-Verbumia host (or to a pre-1.0.5 react-i18next) keeps\n * working — runtime language re-sync is just disabled. From feedback\n * ≥0.2.6, the peerDep on `@verbumia/react-i18next` is `>=1.0.5`, so the\n * lang-change subscription path is guaranteed available in matched\n * installs. */\nexport interface I18nPluginContext {\n i18n?: { language?: string };\n config: {\n apiBase?: string;\n projectUuid: string;\n defaultLocale: string;\n };\n /** #806 — subscribe to runtime language changes (see VerbumiaPluginContext\n * in `@verbumia/react-i18next` ≥1.0.5). Returns an unsubscribe fn the\n * plugin MUST call from teardown. Optional so attaching to a\n * pre-1.0.5 react-i18next falls back gracefully (no auto re-sync;\n * consumer can still pass `options.language` explicitly). */\n onLanguageChange?: (cb: (lng: string) => void) => () => void;\n}\nexport interface I18nPlugin {\n name: string;\n setup?: (ctx: I18nPluginContext) => void | (() => void);\n render?: () => ReactNode;\n}\n\nexport interface FeedbackController {\n open: () => void;\n close: () => void;\n /** The underlying client (advanced; usually unused). */\n client: FeedbackClient;\n}\n\nexport interface FeedbackPluginOptions {\n // NOTE: no `tosVersion` — it is an SDK build-time constant\n // (SDK_TOS_VERSION, task 616), sent automatically; never integrator-set.\n /** Override language; defaults to the i18n provider's defaultLocale. */\n language?: string;\n /** Override apiBase; defaults to the i18n provider's apiBase. */\n apiBase?: string;\n /** Override projectId; defaults to the i18n provider's projectUuid. */\n projectId?: string;\n /** Optional opaque end-user id (server generates one when absent). */\n endUserId?: string;\n /** Explicit on-screen keys (else the i18n key registry, if present). */\n keys?: DeclaredKey[];\n /** Optional namespace filter (CONTRACT v6, additive). When set (a\n * single namespace or a list) the panel shows only resolved keys in\n * that namespace — applied AFTER rendered-scoping (`rendered ∩\n * namespace`). Unset ⇒ all resolved keys (v5 behaviour). */\n namespace?: string | string[];\n /** Receives the imperative { open, close } handle for the host CTA. */\n onReady?: (controller: FeedbackController) => void;\n /** Alt delivery: a ref object that receives the controller. */\n controllerRef?: { current: FeedbackController | null };\n /** Injected fetch (tests / RN polyfills). */\n fetchImpl?: typeof fetch;\n}\n\nfunction makeStore() {\n let open = false;\n const listeners = new Set<() => void>();\n return {\n isOpen: () => open,\n set(v: boolean) {\n if (open !== v) {\n open = v;\n listeners.forEach((l) => l());\n }\n },\n subscribe(l: () => void) {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n };\n}\n\nexport function feedbackPlugin(options: FeedbackPluginOptions): I18nPlugin {\n const store = makeStore();\n let client: FeedbackClient | null = null;\n\n function Outlet() {\n const isOpen = useSyncExternalStore(\n store.subscribe,\n store.isOpen,\n store.isOpen,\n );\n if (!isOpen || !client || typeof document === \"undefined\") return null;\n const c = client;\n return createPortal(\n <FeedbackPanel\n client={c}\n keys={options.keys}\n namespace={options.namespace}\n onClose={() => {\n store.set(false);\n void c.flush();\n }}\n />,\n document.body,\n );\n }\n\n return {\n name: \"@verbumia/feedback\",\n setup(ctx) {\n // #806 SeedSower lang-change init source: prefer an explicit\n // plugin option, then the i18n provider's CURRENT language (so\n // mounts that happen after a boot-time language flip get it\n // right), then the boot-snapshot defaultLocale as the floor.\n const initialLanguage =\n options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;\n client = new FeedbackClient({\n apiBase:\n options.apiBase ?? ctx.config.apiBase ?? \"https://api.verbumia.dev\",\n projectId: options.projectId ?? ctx.config.projectUuid,\n language: initialLanguage,\n endUserId: options.endUserId,\n fetchImpl: options.fetchImpl,\n });\n // #806 — re-sync the client whenever the host changes the\n // language at runtime. Optional: if the host's i18n provider\n // predates the plugin-context `onLanguageChange` field (or the\n // plugin is mounted on a non-Verbumia host), this just no-ops\n // and the consumer can still pass `options.language` explicitly.\n let langUnsub: (() => void) | undefined;\n if (typeof ctx.onLanguageChange === \"function\") {\n langUnsub = ctx.onLanguageChange((lng) => client?.setLanguage(lng));\n }\n const controller: FeedbackController = {\n open: () => store.set(true),\n close: () => {\n store.set(false);\n void client?.flush();\n },\n client,\n };\n options.onReady?.(controller);\n if (options.controllerRef) options.controllerRef.current = controller;\n return () => {\n langUnsub?.();\n if (options.controllerRef) options.controllerRef.current = null;\n void client?.flush();\n };\n },\n render: () => <Outlet />,\n };\n}\n","/**\n * Wire types for the Verbumia End-User Translation Evaluation API.\n * Canonical reference: ./CONTRACT.md (frozen). Backend task 591.\n */\n\nexport interface FeedbackConfig {\n /** API base, no trailing slash needed. e.g. https://api.verbumia.dev */\n apiBase: string;\n /** Public project UUID the widget targets. */\n projectId: string;\n /**\n * sessionId / grouping_key is MINTED SERVER-SIDE by the Verbumia\n * backend at POST /v1/feedback/tos and returned in the token bundle\n * (`TokenBundle.grouping_key`). The widget receives + sends it; it\n * MUST NOT self-generate it. There is intentionally no client config\n * field for it.\n */\n /**\n * NOTE: there is intentionally NO `tosVersion` field. The ToS version\n * is a BUILD-TIME constant baked into @verbumia/feedback at release\n * (`SDK_TOS_VERSION`, task 616) — integrators do not set it; the SDK\n * sends it automatically.\n */\n /** Optional opaque end-user id; server generates one when absent. */\n endUserId?: string;\n /** BCP-47 language the widget rates strings in (e.g. \"fr\"). */\n language: string;\n /** Optional locale tag stored for analytics. */\n locale?: string;\n /** Debounce window (ms) before a queued batch is flushed. Default 1500. */\n flushDebounceMs?: number;\n /** Max queued items before an immediate flush. Default 20. */\n maxBatch?: number;\n /** Injected fetch (tests / RN polyfills). Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface TokenBundle {\n access_token: string;\n token_type: \"Bearer\";\n expires_in: number;\n refresh_token: string;\n refresh_expires_in: number;\n end_user_id: string;\n tos_version: string;\n grouping_key: string;\n}\n\nexport interface FeedbackString {\n namespace: string;\n key: string;\n key_uuid: string;\n language_uuid: string;\n value: string;\n translation_hash: string;\n avg_stars: number | null;\n ratings_count: number;\n my_rating: number | null;\n}\n\nexport interface StringsResponse {\n project_id: string;\n language: string;\n strings: FeedbackString[];\n}\n\n/** A 5-star rating queued for a rendered string variant. */\nexport interface RatingInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n stars: number; // 1..5\n}\n\n/** A suggested alternative translation queued for moderation. */\nexport interface SuggestionInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n suggested_text: string;\n comment?: string;\n}\n\nexport interface BatchResponse {\n accepted: number;\n rejected: number;\n items: Array<Record<string, unknown>>;\n}\n\n/** A key the host app declares as on-screen. */\nexport interface DeclaredKey {\n namespace: string;\n key: string;\n}\n\nexport class FeedbackError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly code?: string,\n ) {\n super(message);\n this.name = \"FeedbackError\";\n }\n}\n","/**\n * BUILD-TIME ToS version constant (task 616, human decision option B).\n *\n * This is baked into the @verbumia/feedback PACKAGE at release. It is\n * NOT an integrator-set config field and NOT server-driven: a stale\n * (e.g. native) app must record consent to the ToS version IT shipped\n * and displayed, for legal correctness — never backend-latest.\n *\n * Releasing a ToS-TEXT change ⇒ bump this constant + cut a new\n * @verbumia/feedback release (per the human/real-CI publish handoff).\n * The backend accepts any version in its acceptable set and records it.\n */\nexport const SDK_TOS_VERSION = \"2026-05-18\";\n","/**\n * Framework-agnostic Verbumia feedback client.\n *\n * Responsibilities:\n * - versioned-ToS acceptance -> scoped end-user JWT bootstrap\n * - transparent access-token refresh (rotating refresh token)\n * - fetch on-screen strings for the widget\n * - debounced + size-capped batched POST of ratings / suggestions,\n * mirroring the missing-handler transport contract (ltm 280):\n * best-effort, never throws into the host app render path.\n *\n * The React and React Native entry points are thin UI shells over this.\n */\n\nimport {\n type BatchResponse,\n type FeedbackConfig,\n FeedbackError,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"./types\";\nimport { SDK_TOS_VERSION } from \"./tos\";\n\nconst SDK_LIB = \"@verbumia/feedback\";\nconst SDK_VER = \"0.2.0\";\n\ntype QueueItem =\n | { kind: \"rating\"; payload: RatingInput }\n | { kind: \"suggestion\"; payload: SuggestionInput };\n\nexport class FeedbackClient {\n private cfg: Required<\n Pick<FeedbackConfig, \"flushDebounceMs\" | \"maxBatch\">\n > &\n FeedbackConfig;\n private fetchImpl: typeof fetch;\n private tokens: TokenBundle | null = null;\n private queue: QueueItem[] = [];\n private timer: ReturnType<typeof setTimeout> | null = null;\n private bootstrapping: Promise<TokenBundle> | null = null;\n\n constructor(config: FeedbackConfig) {\n this.cfg = {\n flushDebounceMs: 1500,\n maxBatch: 20,\n ...config,\n };\n const f = config.fetchImpl ?? globalThis.fetch;\n if (!f) {\n throw new FeedbackError(\n \"no fetch implementation available; pass config.fetchImpl\",\n );\n }\n this.fetchImpl = f.bind(globalThis);\n }\n\n private base(): string {\n return this.cfg.apiBase.replace(/\\/+$/, \"\");\n }\n\n get endUserId(): string | undefined {\n return this.tokens?.end_user_id ?? this.cfg.endUserId;\n }\n\n get hasConsented(): boolean {\n return this.tokens !== null;\n }\n\n /** Server-minted sessionId / grouping_key (from the token bundle).\n * Available only after `acceptTos()`; never client-generated. */\n get sessionId(): string | undefined {\n return this.tokens?.grouping_key;\n }\n\n /** BCP-47 language the widget is rating strings in. */\n get language(): string {\n return this.cfg.language;\n }\n\n /**\n * Re-target the widget's working language at runtime (#806 SeedSower\n * lang-change propagation). Subsequent `getStrings` / `rate` /\n * `suggest` use the new language without rebuilding the client. The\n * server-minted ToS bundle has no `locale` field, so a language flip\n * does NOT invalidate consent — only the next `?language=` query\n * needs updating. The i18n provider plugin (feedback ≥0.2.6)\n * subscribes to `@verbumia/react-i18next` ≥1.0.5's\n * `VerbumiaPluginContext.onLanguageChange` and calls this on every\n * runtime language change so the host never has to wire it manually.\n */\n setLanguage(lang: string): void {\n if (typeof lang !== \"string\" || lang.length === 0) return;\n if (lang === this.cfg.language) return;\n this.cfg = { ...this.cfg, language: lang };\n }\n\n /** ToS version the end user is asked to accept — the SDK's\n * build-time constant (task 616). NOT integrator/server set. */\n get tosVersion(): string {\n return SDK_TOS_VERSION;\n }\n\n /**\n * Accept the ToS and bootstrap a scoped token. Idempotent: a second call\n * returns the in-flight / existing bundle rather than re-accepting.\n */\n async acceptTos(): Promise<TokenBundle> {\n if (this.tokens) return this.tokens;\n if (this.bootstrapping) return this.bootstrapping;\n this.bootstrapping = (async () => {\n const res = await this.fetchImpl(`${this.base()}/v1/feedback/tos`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n // NO grouping_key: server-minted (task 599). It comes back in\n // the token bundle and is bound into the JWT server-side.\n project_id: this.cfg.projectId,\n end_user_id: this.cfg.endUserId,\n tos_version: SDK_TOS_VERSION,\n locale: this.cfg.locale,\n }),\n });\n if (!res.ok) throw await this.problem(res, \"tos acceptance failed\");\n this.tokens = (await res.json()) as TokenBundle;\n return this.tokens;\n })();\n try {\n return await this.bootstrapping;\n } finally {\n this.bootstrapping = null;\n }\n }\n\n private async refresh(): Promise<void> {\n if (!this.tokens) throw new FeedbackError(\"not consented\");\n const res = await this.fetchImpl(\n `${this.base()}/v1/feedback/token/refresh`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refresh_token: this.tokens.refresh_token }),\n },\n );\n if (!res.ok) {\n this.tokens = null; // force a fresh ToS step\n throw await this.problem(res, \"token refresh failed\");\n }\n this.tokens = (await res.json()) as TokenBundle;\n }\n\n /** Authenticated fetch with a single transparent refresh-on-401 retry. */\n private async authed(\n path: string,\n init: RequestInit,\n retry = true,\n ): Promise<Response> {\n if (!this.tokens) await this.acceptTos();\n const res = await this.fetchImpl(`${this.base()}${path}`, {\n ...init,\n headers: {\n ...(init.headers ?? {}),\n Authorization: `Bearer ${this.tokens!.access_token}`,\n },\n });\n if (res.status === 401 && retry) {\n await this.refresh();\n return this.authed(path, init, false);\n }\n return res;\n }\n\n /** Strings rendered on the current view, with this end user's prior rating. */\n async getStrings(opts?: {\n keys?: Array<{ namespace: string; key: string }>;\n namespace?: string;\n limit?: number;\n }): Promise<StringsResponse> {\n // #806 SeedSower P1 (Hermes URLSearchParams polyfill): React Native's\n // bundled URLSearchParams implements ONLY the constructor + toString().\n // `.set` / `.get` / `.append` / `.delete` / `.has` / `.entries` /\n // `.forEach` / `.keys` / `.values` / the iterator all throw on Hermes\n // (documented RN limitation since ~0.50, refused for bundle-size).\n // Build a plain Record<string, string> first, then pass to the\n // constructor — that variant is supported on Node + browser + Hermes\n // + JSC, and produces the same query string. NO mutation after\n // construction anywhere in the SDK.\n const params: Record<string, string> = { language: this.cfg.language };\n if (opts?.keys?.length) {\n params.keys = opts.keys.map((k) => `${k.namespace}:${k.key}`).join(\",\");\n }\n if (opts?.namespace) params.namespace = opts.namespace;\n if (opts?.limit) params.limit = String(opts.limit);\n const qs = new URLSearchParams(params);\n const res = await this.authed(`/v1/feedback/strings?${qs.toString()}`, {\n method: \"GET\",\n });\n if (!res.ok) throw await this.problem(res, \"failed to load strings\");\n return (await res.json()) as StringsResponse;\n }\n\n /** Queue a rating; flushed on debounce or when the batch fills. */\n rate(payload: RatingInput): void {\n this.enqueue({ kind: \"rating\", payload });\n }\n\n /** Queue a suggestion; flushed on debounce or when the batch fills. */\n suggest(payload: SuggestionInput): void {\n this.enqueue({ kind: \"suggestion\", payload });\n }\n\n private enqueue(item: QueueItem): void {\n this.queue.push(item);\n if (this.queue.length >= this.cfg.maxBatch) {\n void this.flush();\n return;\n }\n if (this.timer) clearTimeout(this.timer);\n this.timer = setTimeout(() => void this.flush(), this.cfg.flushDebounceMs);\n }\n\n /**\n * Flush queued items. Best-effort: a transport/auth failure re-queues the\n * batch once and swallows the error so the host app never sees a throw.\n */\n async flush(): Promise<void> {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n if (!this.queue.length) return;\n const batch = this.queue;\n this.queue = [];\n const ratings = batch\n .filter((b) => b.kind === \"rating\")\n .map((b) => b.payload as RatingInput);\n const suggestions = batch\n .filter((b) => b.kind === \"suggestion\")\n .map((b) => b.payload as SuggestionInput);\n try {\n if (ratings.length) {\n await this.postBatch(\"/v1/feedback/ratings\", { ratings });\n }\n if (suggestions.length) {\n await this.postBatch(\"/v1/feedback/suggestions\", { suggestions });\n }\n } catch {\n // Re-queue once; feedback must never break the host app.\n this.queue.unshift(...batch);\n }\n }\n\n private async postBatch(\n path: string,\n body: unknown,\n ): Promise<BatchResponse> {\n const res = await this.authed(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-SDK\": `${SDK_LIB}@${SDK_VER}` },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw await this.problem(res, \"batch post failed\");\n return (await res.json()) as BatchResponse;\n }\n\n private async problem(res: Response, fallback: string): Promise<FeedbackError> {\n let code: string | undefined;\n let detail = fallback;\n try {\n const body = (await res.json()) as { code?: string; detail?: string };\n code = body.code;\n if (body.detail) detail = body.detail;\n } catch {\n /* non-JSON body */\n }\n return new FeedbackError(detail, res.status, code);\n }\n}\n","import { useCallback, useEffect, useState } from \"react\";\n\nimport type { FeedbackClient } from \"../core/client\";\nimport { resolveKeys } from \"../core/keys\";\nimport type { DeclaredKey, FeedbackString } from \"../core/types\";\n\nconst C = {\n bg: \"#0b0f0e\",\n panel: \"#111714\",\n border: \"#1f2a25\",\n text: \"#e7f5ef\",\n dim: \"#8aa79b\",\n emerald: \"#10b981\",\n emeraldSoft: \"#34d399\",\n};\n\nexport function FeedbackPanel(props: {\n client: FeedbackClient;\n keys?: DeclaredKey[];\n namespace?: string | string[];\n onClose: () => void;\n}) {\n const { client, keys, namespace, onClose } = props;\n const [consented, setConsented] = useState(client.hasConsented);\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [strings, setStrings] = useState<FeedbackString[]>([]);\n\n const loadStrings = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n // On-screen scoping is NORMATIVE (spec ltm 373, CONTRACT v5): show\n // ONLY rendered keys. Empty -> \"no strings on this view\"; never\n // fall back to fetching the whole project.\n const resolved = resolveKeys(keys, namespace);\n if (!resolved.length) {\n // #806 SeedSower P1 — when the panel opens with NO explicit\n // `keys` prop (i.e. it depended on the i18n SDK's on-screen\n // registry), an empty resolve almost always means the host's\n // `useTranslation` imports went to `react-i18next` instead of\n // `@verbumia/react-i18next`. Either way, the keys never fed the\n // registry. Log a single, actionable hint so the integrator\n // doesn't waste a debug cycle on \"the widget is broken\".\n if (!keys || keys.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[verbumia/feedback] registry empty — are your useTranslation imports coming from @verbumia/react-i18next?\",\n );\n }\n setStrings([]);\n return;\n }\n const res = await client.getStrings({ keys: resolved });\n setStrings(res.strings);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Failed to load strings\");\n } finally {\n setBusy(false);\n }\n }, [client, keys, namespace]);\n\n useEffect(() => {\n if (consented) void loadStrings();\n }, [consented, loadStrings]);\n\n const accept = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n await client.acceptTos();\n setConsented(true);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Could not accept the terms\");\n } finally {\n setBusy(false);\n }\n }, [client]);\n\n return (\n <div\n role=\"dialog\"\n aria-label=\"Translation feedback\"\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483600,\n background: \"rgba(0,0,0,.55)\",\n display: \"flex\",\n justifyContent: \"flex-end\",\n }}\n onClick={onClose}\n >\n <div\n onClick={(e) => e.stopPropagation()}\n style={{\n width: \"min(420px, 100%)\",\n height: \"100%\",\n background: C.bg,\n color: C.text,\n borderLeft: `1px solid ${C.border}`,\n display: \"flex\",\n flexDirection: \"column\",\n fontFamily: \"system-ui, sans-serif\",\n }}\n >\n <header\n style={{\n padding: \"16px 18px\",\n borderBottom: `1px solid ${C.border}`,\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n }}\n >\n <strong style={{ color: C.emeraldSoft }}>Translation feedback</strong>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n style={{\n background: \"transparent\",\n color: C.dim,\n border: \"none\",\n fontSize: 20,\n cursor: \"pointer\",\n }}\n >\n ×\n </button>\n </header>\n\n <div style={{ padding: 18, overflowY: \"auto\", flex: 1 }}>\n {error && (\n <p style={{ color: \"#f87171\", fontSize: 13 }}>{error}</p>\n )}\n\n {!consented ? (\n <ConsentStep\n version={client.tosVersion}\n busy={busy}\n onAccept={accept}\n />\n ) : busy && !strings.length ? (\n <p style={{ color: C.dim }}>Loading…</p>\n ) : !strings.length ? (\n <p style={{ color: C.dim }}>No strings to review on this view.</p>\n ) : (\n strings.map((s) => (\n <StringRow key={`${s.namespace}:${s.key}`} s={s} client={client} />\n ))\n )}\n </div>\n <footer\n style={{\n padding: \"10px 18px\",\n borderTop: `1px solid ${C.border}`,\n color: C.dim,\n fontSize: 11,\n }}\n >\n Powered by Verbumia\n </footer>\n </div>\n </div>\n );\n}\n\nfunction ConsentStep(props: {\n version?: string;\n busy: boolean;\n onAccept: () => void;\n}) {\n return (\n <div>\n <p style={{ lineHeight: 1.5, fontSize: 14 }}>\n Help improve the translations in this app. Your ratings and\n suggestions are sent to the app owner via Verbumia. Don’t submit\n personal or sensitive information.\n </p>\n <button\n type=\"button\"\n disabled={props.busy}\n onClick={props.onAccept}\n style={{\n marginTop: 12,\n width: \"100%\",\n background: C.emerald,\n color: \"#03110c\",\n border: \"none\",\n borderRadius: 8,\n padding: \"12px 14px\",\n fontWeight: 700,\n cursor: props.busy ? \"default\" : \"pointer\",\n opacity: props.busy ? 0.6 : 1,\n }}\n >\n {props.busy\n ? \"…\"\n : `I accept the terms${props.version ? ` (v${props.version})` : \"\"}`}\n </button>\n </div>\n );\n}\n\nfunction StringRow(props: { s: FeedbackString; client: FeedbackClient }) {\n const { s, client } = props;\n const [mine, setMine] = useState<number | null>(s.my_rating);\n const [showSuggest, setShowSuggest] = useState(false);\n const [text, setText] = useState(\"\");\n const [sent, setSent] = useState(false);\n\n const rate = (stars: number) => {\n setMine(stars);\n client.rate({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n stars,\n });\n };\n\n const submitSuggestion = () => {\n if (!text.trim()) return;\n client.suggest({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n suggested_text: text.trim(),\n });\n setSent(true);\n setShowSuggest(false);\n setText(\"\");\n };\n\n return (\n <div\n style={{\n background: C.panel,\n border: `1px solid ${C.border}`,\n borderRadius: 10,\n padding: 12,\n marginBottom: 10,\n }}\n >\n <div style={{ fontSize: 12, color: C.dim }}>\n {s.namespace} · {s.key}\n </div>\n <div style={{ margin: \"6px 0 10px\", fontSize: 15 }}>{s.value}</div>\n <div style={{ display: \"flex\", gap: 4 }}>\n {[1, 2, 3, 4, 5].map((n) => (\n <button\n key={n}\n type=\"button\"\n aria-label={`${n} star${n > 1 ? \"s\" : \"\"}`}\n onClick={() => rate(n)}\n style={{\n background: \"transparent\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: 20,\n color: mine && n <= mine ? C.emeraldSoft : C.border,\n }}\n >\n ★\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowSuggest((v) => !v)}\n style={{\n marginLeft: \"auto\",\n background: \"transparent\",\n color: C.emeraldSoft,\n border: `1px solid ${C.border}`,\n borderRadius: 6,\n padding: \"4px 10px\",\n fontSize: 12,\n cursor: \"pointer\",\n }}\n >\n {sent ? \"Suggested ✓\" : \"Suggest\"}\n </button>\n </div>\n {showSuggest && (\n <div style={{ marginTop: 10 }}>\n <textarea\n value={text}\n onChange={(e) => setText(e.target.value)}\n rows={3}\n placeholder=\"Your suggested translation…\"\n style={{\n width: \"100%\",\n background: C.bg,\n color: C.text,\n border: `1px solid ${C.border}`,\n borderRadius: 6,\n padding: 8,\n fontSize: 14,\n resize: \"vertical\",\n }}\n />\n <button\n type=\"button\"\n onClick={submitSuggestion}\n style={{\n marginTop: 6,\n background: C.emerald,\n color: \"#03110c\",\n border: \"none\",\n borderRadius: 6,\n padding: \"8px 12px\",\n fontWeight: 700,\n cursor: \"pointer\",\n }}\n >\n Send suggestion\n </button>\n </div>\n )}\n </div>\n );\n}\n","/**\n * On-screen key discovery.\n *\n * Preferred source: the `@verbumia/*-i18n` SDK exposes a lightweight key\n * registry of keys it has rendered. When that registry is present on the\n * global (the i18n SDK publishes `globalThis.__verbumia_key_registry__`),\n * we read the keys touched on the current view. Otherwise the host app\n * passes an explicit `keys` list — the always-available fallback.\n *\n * The registry contract is intentionally tiny so any framework port of the\n * i18n SDK can implement it without depending on this package.\n */\n\nimport type { DeclaredKey } from \"./types\";\n\nconst REGISTRY_GLOBAL = \"__verbumia_key_registry__\";\n\ninterface KeyRegistry {\n /** Returns the keys rendered since the last `reset()` (or ever). */\n snapshot(): DeclaredKey[];\n reset?(): void;\n}\n\nfunction getRegistry(): KeyRegistry | null {\n const g = globalThis as Record<string, unknown>;\n const reg = g[REGISTRY_GLOBAL];\n if (\n reg &&\n typeof (reg as KeyRegistry).snapshot === \"function\"\n ) {\n return reg as KeyRegistry;\n }\n return null;\n}\n\n/** True when a compatible `@verbumia/*-i18n` registry is detectable. */\nexport function hasKeyRegistry(): boolean {\n return getRegistry() !== null;\n}\n\n/**\n * Resolve the on-screen keys: explicit `keys` prop always wins (it is the\n * customer's authoritative declaration); otherwise fall back to the i18n\n * registry snapshot; otherwise an empty list (widget shows \"no strings\").\n *\n * Optional `namespace` filter (task 618, CONTRACT v6 — ADDITIVE): when\n * set (a single namespace or a list), the resolved set is narrowed to\n * keys whose `namespace` is in the filter. The filter is applied AFTER\n * resolution, so it composes with v5 rendered-scoping as\n * `rendered ∩ namespace`. UNSET / empty ⇒ unchanged v5 behaviour (all\n * resolved keys). Backward-compatible: `resolveKeys(keys)` is unaffected.\n */\nexport function resolveKeys(\n explicit?: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const base =\n explicit && explicit.length\n ? dedupe(explicit)\n : getRegistry()\n ? dedupe(getRegistry()!.snapshot())\n : [];\n return filterByNamespace(base, namespace);\n}\n\n/** Narrow keys to those whose namespace is in `namespace`. An undefined\n * filter, an empty string, or an empty list means \"no filter\" (the\n * additive default — identical to v5). Exported so non-panel callers\n * (e.g. a custom /core integration) can apply the same semantics. */\nexport function filterByNamespace(\n keys: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const list = (Array.isArray(namespace) ? namespace : namespace ? [namespace] : [])\n .map((n) => n.trim())\n .filter(Boolean);\n if (!list.length) return keys;\n const allow = new Set(list);\n return keys.filter((k) => allow.has(k.namespace));\n}\n\nfunction dedupe(keys: DeclaredKey[]): DeclaredKey[] {\n const seen = new Set<string>();\n const out: DeclaredKey[] = [];\n for (const k of keys) {\n const id = `${k.namespace}:${k.key}`;\n if (!seen.has(id)) {\n seen.add(id);\n out.push(k);\n }\n }\n return out;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/react/index.ts","../../src/react/plugin.tsx","../../src/core/types.ts","../../src/core/tos.ts","../../src/core/client.ts","../../src/react/panel.tsx","../../src/core/keys.ts"],"sourcesContent":["export {\n feedbackPlugin,\n type FeedbackController,\n type FeedbackPluginOptions,\n type I18nPlugin,\n type I18nPluginContext,\n} from \"./plugin\";\nexport { FeedbackPanel } from \"./panel\";\nexport {\n FeedbackClient,\n hasKeyRegistry,\n resolveKeys,\n type BatchResponse,\n type DeclaredKey,\n type FeedbackConfig,\n FeedbackError,\n type FeedbackString,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"../core\";\n","/**\n * `@verbumia/feedback` as a PLUGIN of the `@verbumia/*-i18n` provider.\n *\n * Architecture (task 599): NO second React context. The customer adds\n * `feedbackPlugin(...)` to the i18n provider's `plugins` slot. The\n * provider calls `setup({ i18n, config })` once (we reuse its apiBase /\n * projectUuid / locale — no re-config) and renders our `render()` as an\n * ISOLATED sibling leaf. The panel's open/close lives in a tiny private\n * store the outlet subscribes to via useSyncExternalStore, so toggling\n * feedback NEVER re-renders the host app. The customer triggers it via\n * their own CTA through the imperative controller.\n */\nimport { createPortal } from \"react-dom\";\nimport { useSyncExternalStore, type ReactNode } from \"react\";\n\nimport { FeedbackClient } from \"../core/client\";\nimport type { DeclaredKey, FeedbackAddonState } from \"../core/types\";\nimport { FeedbackPanel } from \"./panel\";\n\n/** Structural mirror of `@verbumia/react-i18next`'s VerbumiaPlugin —\n * kept local so feedback has no build-time dep on the i18n SDK. The\n * `i18n` and `onLanguageChange` fields are optional so a plugin attached\n * to a non-Verbumia host (or to a pre-1.0.5 react-i18next) keeps\n * working — runtime language re-sync is just disabled. From feedback\n * ≥0.2.6, the peerDep on `@verbumia/react-i18next` is `>=1.0.5`, so the\n * lang-change subscription path is guaranteed available in matched\n * installs. */\nexport interface I18nPluginContext {\n i18n?: {\n language?: string;\n /** 0.2.7 — direct handle on the underlying `i18next` instance the\n * `@verbumia/react-i18next` provider exposes. Used by the\n * `scope: \"current-view\"` snapshot path: a same-locale\n * `changeLanguage(language)` re-emits `languageChanged` so every\n * bound consumer re-renders → `t()` calls repopulate the registry.\n * Optional so the plugin still works on non-Verbumia hosts (it\n * falls back to a no-rerender registry snapshot in that case). */\n i18next?: {\n language?: string;\n changeLanguage?: (lng: string) => Promise<unknown>;\n };\n };\n config: {\n apiBase?: string;\n projectUuid: string;\n defaultLocale: string;\n };\n /** #806 — subscribe to runtime language changes (see VerbumiaPluginContext\n * in `@verbumia/react-i18next` ≥1.0.5). Returns an unsubscribe fn the\n * plugin MUST call from teardown. Optional so attaching to a\n * pre-1.0.5 react-i18next falls back gracefully (no auto re-sync;\n * consumer can still pass `options.language` explicitly). */\n onLanguageChange?: (cb: (lng: string) => void) => () => void;\n}\nexport interface I18nPlugin {\n name: string;\n setup?: (ctx: I18nPluginContext) => void | (() => void);\n render?: () => ReactNode;\n}\n\nexport interface FeedbackController {\n /**\n * Opens the panel. From 0.2.7 the call is **async** (returns\n * `Promise<void>`) so the `scope: \"current-view\"` snapshot sequence\n * can run before the modal mounts (reset registry → force\n * languageChanged → 1-frame yield → snapshot). Hosts that do NOT\n * await still work — the modal mounts when the promise resolves.\n * Under `scope: \"all\"` (or when the i18next instance isn't reachable)\n * `open()` resolves on the next microtask with no extra work.\n */\n open: () => Promise<void>;\n close: () => void;\n /** The underlying client (advanced; usually unused). */\n client: FeedbackClient;\n /** ToS version currently required by the backend (SDK build-time\n * constant — see core/tos.ts). 0.2.7+. */\n readonly tosVersion: string;\n /** Whether this end-user has a live session-token bundle (i.e. has\n * accepted the current ToS version). Mirrors the same persisted state\n * the built-in modal writes to, so an external ToS page that calls\n * `acceptTos()` flips this to `true` immediately. 0.2.7+. */\n readonly hasAcceptedTos: boolean;\n /** Programmatically POST `/v1/feedback/tos` and persist the returned\n * token bundle into the SDK's shared store. Used by hosts that\n * build their own ToS page (`feedbackPlugin({ tos: \"skip\" })`). Thin\n * alias over `FeedbackClient.acceptTos()` — idempotent (a second\n * call returns the in-flight / existing bundle without re-POSTing).\n * 0.2.7+. */\n acceptTos: () => Promise<void>;\n /** 0.2.7 — feedback-addon active flag for this project, sourced from\n * `GET /v1/projects/{p}/feedback-addon/state` + composed with the\n * plugin's `cta` option:\n * - `cta: \"auto\"` (default): mirrors `state.isActive` (so a host\n * that hides its CTA on `!isActive` does the right thing for\n * Starter / no SKU).\n * - `cta: \"show\"`: always `true` (force the host's CTA).\n * - `cta: \"hide\"`: always `false`.\n * `null` before the first state fetch resolves (e.g. on a host that\n * didn't pass `apiKey` and hasn't accepted ToS yet). */\n readonly isActive: boolean | null;\n /** 0.2.7 — the languages the SDK is licensed to rate strings in.\n * Mirrors `state.enabledLanguages` verbatim: `string[]` for Starter\n * / no SKU, literal `\"all\"` for Unlimited. `null` before the first\n * state fetch resolves. */\n readonly enabledLanguages: string[] | \"all\" | null;\n /** 0.2.7 — raw SKU code (`feedback_starter` / `feedback_unlimited`\n * / `null`). Useful for downstream UI / billing dashboards. */\n readonly sku: \"feedback_starter\" | \"feedback_unlimited\" | null;\n}\n\nexport interface FeedbackPluginOptions {\n // NOTE: no `tosVersion` — it is an SDK build-time constant\n // (SDK_TOS_VERSION, task 616), sent automatically; never integrator-set.\n /** Override language; defaults to the i18n provider's defaultLocale. */\n language?: string;\n /** Override apiBase; defaults to the i18n provider's apiBase. */\n apiBase?: string;\n /** Override projectId; defaults to the i18n provider's projectUuid. */\n projectId?: string;\n /** Optional opaque end-user id (server generates one when absent). */\n endUserId?: string;\n /** Explicit on-screen keys (else the i18n key registry, if present). */\n keys?: DeclaredKey[];\n /** Optional namespace filter (CONTRACT v6, additive). When set (a\n * single namespace or a list) the panel shows only resolved keys in\n * that namespace — applied AFTER rendered-scoping (`rendered ∩\n * namespace`). Unset ⇒ all resolved keys (v5 behaviour). */\n namespace?: string | string[];\n /** Receives the imperative { open, close } handle for the host CTA. */\n onReady?: (controller: FeedbackController) => void;\n /** Alt delivery: a ref object that receives the controller. */\n controllerRef?: { current: FeedbackController | null };\n /** Injected fetch (tests / RN polyfills). */\n fetchImpl?: typeof fetch;\n /**\n * 0.2.7 — how the SDK handles the Terms of Service prompt:\n * - `\"modal\"` (DEFAULT, backwards-compatible): the built-in modal\n * renders the SDK's ToS step on first open; tapping Accept calls\n * `POST /v1/feedback/tos` and persists the token bundle.\n * - `\"skip\"`: the built-in modal SKIPS the ToS step entirely. The\n * host promises to handle consent externally (e.g. their own\n * branded ToS page) and must call `controller.acceptTos()` to\n * mint the session bundle. Until that runs, every `getStrings()`\n * surfaces the existing 401 error state (the SDK does NOT\n * silently auto-accept on the user's behalf).\n */\n tos?: \"modal\" | \"skip\";\n /**\n * 0.2.7 — host's project API key (`vrb_live_…`, scope `project:read`).\n * Lets the SDK call `GET /v1/projects/{p}/feedback-addon/state` at\n * `setup()` time + on language change, BEFORE the end-user accepts\n * ToS. When omitted, addon-state is deferred until the user's\n * session bearer is minted via `acceptTos()`, and `controller.isActive`\n * stays `null` until then.\n */\n apiKey?: string;\n /**\n * 0.2.7 — controls `controller.isActive` (which hosts wire to their\n * own CTA's `hidden` flag — the SDK does not render a floating CTA\n * itself; see plugin.tsx header).\n * - `\"auto\"` (DEFAULT): mirrors `state.isActive` from the backend,\n * i.e. hides on Starter / no SKU, shows on Unlimited.\n * - `\"show\"`: forces `isActive=true` regardless of state.\n * - `\"hide\"`: forces `isActive=false` regardless of state.\n */\n cta?: \"auto\" | \"show\" | \"hide\";\n /**\n * 0.2.7 — what the panel calls \"on-screen keys\":\n * - `\"current-view\"` (DEFAULT — rebalances the 1.0.3 \"strict-better-\n * than-false-empty\" trade-off now that we have a better mechanism):\n * `controller.open()` (now async) resets the on-screen key\n * registry, force-emits a same-locale `languageChanged` through\n * i18next to re-render every consumer, awaits a 1-frame yield so\n * their `t()` calls repopulate the registry, snapshots, then opens\n * the modal with that snapshot. Result: the widget lists ONLY keys\n * rendered on the user's current screen at the instant they\n * opened it.\n * - `\"all\"`: pre-0.2.7 behavior — no reset, no force re-render. The\n * panel reads the accumulated registry as-is (still strictly\n * better than a false-empty; useful if you want the cumulative\n * keys-since-mount view).\n *\n * KNOWN LIMITATION (`\"current-view\"`): components that call\n * `i18next.t()` OUTSIDE a React tree (cron-like calls, module-load\n * `t()`) are NOT re-collected by the changeLanguage trigger — they\n * have nothing to re-render. Acceptable: those calls aren't strictly\n * \"rendered on screen\", so excluding them matches the on-screen\n * contract.\n */\n scope?: \"current-view\" | \"all\";\n}\n\n/** 0.2.7 — panel store. `snapshotKeys` is the on-screen snapshot captured\n * by `controller.open()` when `scope: \"current-view\"`; the Outlet passes\n * it down as the panel's `keys` prop, where the precedence becomes\n * `options.keys` > snapshot > registry. `getSnapshot` must return a\n * stable reference between notifications (the useSyncExternalStore\n * contract), so the state object is reallocated only on real changes. */\ntype PanelState = {\n isOpen: boolean;\n snapshotKeys: DeclaredKey[] | undefined;\n};\nfunction makeStore() {\n let state: PanelState = { isOpen: false, snapshotKeys: undefined };\n const listeners = new Set<() => void>();\n return {\n getState: (): PanelState => state,\n setOpen(open: boolean, snapshotKeys?: DeclaredKey[]): void {\n const next: PanelState = {\n isOpen: open,\n snapshotKeys: open ? snapshotKeys : undefined,\n };\n if (\n state.isOpen === next.isOpen &&\n state.snapshotKeys === next.snapshotKeys\n ) {\n return;\n }\n state = next;\n listeners.forEach((l) => l());\n },\n subscribe(l: () => void): () => void {\n listeners.add(l);\n return () => {\n listeners.delete(l);\n };\n },\n };\n}\n\n/**\n * 0.2.7 — \"current-view\" snapshot capture. Resets the on-screen key\n * registry, forces every i18next-bound consumer to re-render via a\n * same-locale `changeLanguage` (which re-emits `languageChanged` without\n * triggering a CDN re-fetch — same locale value), awaits a 1-frame\n * yield (rAF on web / 16 ms setTimeout on RN/Hermes) so the re-renders'\n * `t()` calls repopulate the registry, then snapshots. When the i18next\n * instance isn't reachable (non-Verbumia host) returns the current\n * registry snapshot WITHOUT the reset+rerender — strictly better than a\n * false-empty, matches the pre-0.2.7 `scope: \"all\"` behavior.\n */\nasync function captureCurrentViewSnapshot(\n i18next:\n | {\n language?: string;\n changeLanguage?: (lng: string) => Promise<unknown>;\n }\n | undefined,\n): Promise<DeclaredKey[]> {\n type Reg = {\n snapshot: () => DeclaredKey[];\n reset?: () => void;\n };\n const reg = (globalThis as Record<string, unknown>)\n .__verbumia_key_registry__ as Reg | undefined;\n if (!reg) return [];\n const lng = i18next?.language;\n const change = i18next?.changeLanguage;\n if (typeof change === \"function\" && typeof lng === \"string\" && lng) {\n // Reset BEFORE the trigger so the post-rerender repopulation is the\n // only contributor to the snapshot.\n reg.reset?.();\n try {\n await change(lng);\n } catch {\n // Defensive — if i18next throws (e.g. exotic backend), fall\n // through to the no-trigger path: snapshot whatever's there.\n }\n await new Promise<void>((r) => {\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(() => r());\n } else {\n setTimeout(() => r(), 16);\n }\n });\n return reg.snapshot();\n }\n // No reachable i18next — degrade to \"snapshot the accumulator\". Avoids\n // a guaranteed false-empty on non-Verbumia hosts.\n return reg.snapshot();\n}\n\nexport function feedbackPlugin(options: FeedbackPluginOptions): I18nPlugin {\n const store = makeStore();\n let client: FeedbackClient | null = null;\n\n function Outlet() {\n const state = useSyncExternalStore(\n store.subscribe,\n store.getState,\n store.getState,\n );\n if (!state.isOpen || !client || typeof document === \"undefined\") return null;\n const c = client;\n // Precedence: explicit `options.keys` from the host wins (the\n // CONTRACT v5 \"authoritative declaration\"); else the 0.2.7\n // `scope: \"current-view\"` snapshot captured by `controller.open()`;\n // else the panel falls back to the live registry (the pre-0.2.7\n // path, equivalent to `scope: \"all\"`).\n const panelKeys = options.keys ?? state.snapshotKeys;\n return createPortal(\n <FeedbackPanel\n client={c}\n keys={panelKeys}\n namespace={options.namespace}\n tos={options.tos ?? \"modal\"}\n onClose={() => {\n store.setOpen(false);\n void c.flush();\n }}\n />,\n document.body,\n );\n }\n\n return {\n name: \"@verbumia/feedback\",\n setup(ctx) {\n // #806 SeedSower lang-change init source: prefer an explicit\n // plugin option, then the i18n provider's CURRENT language (so\n // mounts that happen after a boot-time language flip get it\n // right), then the boot-snapshot defaultLocale as the floor.\n const initialLanguage =\n options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;\n // 0.2.7 ToS: `tos: \"skip\"` flips `autoAcceptTos: false` so the\n // client refuses to silently `POST /v1/feedback/tos` on the user's\n // behalf — the host owns acceptance via `controller.acceptTos()`.\n const tos: \"modal\" | \"skip\" = options.tos ?? \"modal\";\n client = new FeedbackClient({\n apiBase:\n options.apiBase ?? ctx.config.apiBase ?? \"https://api.verbumia.dev\",\n projectId: options.projectId ?? ctx.config.projectUuid,\n language: initialLanguage,\n endUserId: options.endUserId,\n fetchImpl: options.fetchImpl,\n apiKey: options.apiKey,\n autoAcceptTos: tos !== \"skip\",\n });\n const clientRef = client;\n // 0.2.7 addon-state cache. `null` until the first fetch resolves;\n // a transient transport error leaves the prior value in place\n // (best-effort — never flip the host's CTA on a network blip).\n let addonState: FeedbackAddonState | null = null;\n const cta: \"auto\" | \"show\" | \"hide\" = options.cta ?? \"auto\";\n // 0.2.7 — `scope: \"current-view\"` is the new default. The\n // accumulator behavior from 1.0.3 stays available behind\n // `scope: \"all\"` for hosts that want it (or for non-Verbumia hosts\n // where the changeLanguage trigger can't reach an i18next).\n const scope: \"current-view\" | \"all\" = options.scope ?? \"current-view\";\n const ctxI18next = ctx.i18n?.i18next;\n const refreshState = async (): Promise<void> => {\n try {\n const next = await clientRef.getAddonState();\n if (next !== null) addonState = next;\n } catch {\n // Pre-acceptTos + no apiKey → getAddonState throws synchronously\n // (\"addon state requires apiKey or acceptTos\"). Defer silently;\n // a later acceptTos() will let the plugin retry.\n }\n };\n // Fetch state at setup time when an apiKey is available. Without\n // one, the controller getters stay `null` until the host calls\n // controller.acceptTos() (or the modal does), at which point the\n // bearer path unblocks the fetch — wired below.\n if (options.apiKey) void refreshState();\n // #806 — re-sync the client (and refresh addon-state, since\n // enabledLanguages may differ by language) on runtime language\n // changes. Optional: pre-1.0.5 hosts no-op.\n let langUnsub: (() => void) | undefined;\n if (typeof ctx.onLanguageChange === \"function\") {\n langUnsub = ctx.onLanguageChange((lng) => {\n clientRef.setLanguage(lng);\n void refreshState();\n });\n }\n const controller: FeedbackController = {\n open: async (): Promise<void> => {\n if (scope === \"current-view\") {\n const snapshot = await captureCurrentViewSnapshot(ctxI18next);\n store.setOpen(true, snapshot);\n return;\n }\n // scope === \"all\" → pre-0.2.7 behavior: no reset, no rerender,\n // no snapshot — the panel reads the live registry directly.\n store.setOpen(true);\n },\n close: () => {\n store.setOpen(false);\n void clientRef?.flush();\n },\n client: clientRef,\n get tosVersion(): string {\n return clientRef.tosVersion;\n },\n get hasAcceptedTos(): boolean {\n return clientRef.hasAcceptedTos;\n },\n acceptTos: async (): Promise<void> => {\n await clientRef.acceptTos();\n // Newly-minted bearer unblocks the deferred state fetch.\n if (!options.apiKey) await refreshState();\n },\n get isActive(): boolean | null {\n if (cta === \"show\") return true;\n if (cta === \"hide\") return false;\n // cta === \"auto\"\n return addonState ? addonState.isActive : null;\n },\n get enabledLanguages(): string[] | \"all\" | null {\n return addonState ? addonState.enabledLanguages : null;\n },\n get sku(): \"feedback_starter\" | \"feedback_unlimited\" | null {\n return addonState ? addonState.sku : null;\n },\n };\n options.onReady?.(controller);\n if (options.controllerRef) options.controllerRef.current = controller;\n return () => {\n langUnsub?.();\n if (options.controllerRef) options.controllerRef.current = null;\n void client?.flush();\n };\n },\n render: () => <Outlet />,\n };\n}\n","/**\n * Wire types for the Verbumia End-User Translation Evaluation API.\n * Canonical reference: ./CONTRACT.md (frozen). Backend task 591.\n */\n\nexport interface FeedbackConfig {\n /** API base, no trailing slash needed. e.g. https://api.verbumia.dev */\n apiBase: string;\n /** Public project UUID the widget targets. */\n projectId: string;\n /**\n * sessionId / grouping_key is MINTED SERVER-SIDE by the Verbumia\n * backend at POST /v1/feedback/tos and returned in the token bundle\n * (`TokenBundle.grouping_key`). The widget receives + sends it; it\n * MUST NOT self-generate it. There is intentionally no client config\n * field for it.\n */\n /**\n * NOTE: there is intentionally NO `tosVersion` field. The ToS version\n * is a BUILD-TIME constant baked into @verbumia/feedback at release\n * (`SDK_TOS_VERSION`, task 616) — integrators do not set it; the SDK\n * sends it automatically.\n */\n /** Optional opaque end-user id; server generates one when absent. */\n endUserId?: string;\n /** BCP-47 language the widget rates strings in (e.g. \"fr\"). */\n language: string;\n /** Optional locale tag stored for analytics. */\n locale?: string;\n /** Debounce window (ms) before a queued batch is flushed. Default 1500. */\n flushDebounceMs?: number;\n /** Max queued items before an immediate flush. Default 20. */\n maxBatch?: number;\n /** Injected fetch (tests / RN polyfills). Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /**\n * 0.2.7 — host's project API key (`vrb_live_…`, scope `project:read`).\n * When set, the client can call addon-state at setup time (BEFORE\n * the end-user accepts ToS) using `Authorization: ApiKey …` — mirrors\n * the existing scheme used by the i18n SDK's dev-env loaders. When\n * unset, addon-state is deferred until the user's session bearer is\n * minted via `acceptTos()`.\n */\n apiKey?: string;\n /**\n * #806 / 0.2.7 — when `false`, the client must NOT silently call\n * `POST /v1/feedback/tos` to bootstrap a session on the user's behalf.\n * Set by the plugin when the host opts into `tos: \"skip\"` (the host\n * promises to handle consent externally via `controller.acceptTos()`).\n * `authed()` then throws `FeedbackError(\"not consented\")` instead of\n * auto-accepting — the panel surfaces the existing error state.\n * Default `true` preserves all pre-0.2.7 behaviour.\n */\n autoAcceptTos?: boolean;\n}\n\nexport interface TokenBundle {\n access_token: string;\n token_type: \"Bearer\";\n expires_in: number;\n refresh_token: string;\n refresh_expires_in: number;\n end_user_id: string;\n tos_version: string;\n grouping_key: string;\n}\n\nexport interface FeedbackString {\n namespace: string;\n key: string;\n key_uuid: string;\n language_uuid: string;\n value: string;\n translation_hash: string;\n avg_stars: number | null;\n ratings_count: number;\n my_rating: number | null;\n}\n\nexport interface StringsResponse {\n project_id: string;\n language: string;\n strings: FeedbackString[];\n}\n\n/**\n * 0.2.7 — addon state for a project, returned by\n * `GET /v1/projects/{projectId}/feedback-addon/state`. Drives the\n * controller surface (`isActive`, `enabledLanguages`, `sku`,\n * `languagesLimit`) so the host can hide its own CTA on Starter +\n * narrow the widget to enabled languages.\n *\n * Backend contract (camelCase aliases, populate_by_name=true on the\n * server schema, confirmed against backend task 836 SHA d78b939 + the\n * follow-up PR that extends the auth surface to accept project API\n * keys with `project:read` scope):\n * - `isActive`: org.feedback_addon.active\n * - `sku`: the SKU code, or `null` if no active SKU\n * - `enabledLanguages`: the project's allow-list, or literal `\"all\"`\n * when sku === `\"feedback_unlimited\"`; an empty array when no SKU\n * - `languagesLimit`: hard cap for Starter (3), null otherwise\n */\nexport interface FeedbackAddonState {\n isActive: boolean;\n sku: \"feedback_starter\" | \"feedback_unlimited\" | null;\n enabledLanguages: string[] | \"all\";\n languagesLimit: number | null;\n}\n\n/** A 5-star rating queued for a rendered string variant. */\nexport interface RatingInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n stars: number; // 1..5\n}\n\n/** A suggested alternative translation queued for moderation. */\nexport interface SuggestionInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n suggested_text: string;\n comment?: string;\n}\n\nexport interface BatchResponse {\n accepted: number;\n rejected: number;\n items: Array<Record<string, unknown>>;\n}\n\n/** A key the host app declares as on-screen. */\nexport interface DeclaredKey {\n namespace: string;\n key: string;\n}\n\nexport class FeedbackError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly code?: string,\n ) {\n super(message);\n this.name = \"FeedbackError\";\n }\n}\n","/**\n * BUILD-TIME ToS version constant (task 616, human decision option B).\n *\n * This is baked into the @verbumia/feedback PACKAGE at release. It is\n * NOT an integrator-set config field and NOT server-driven: a stale\n * (e.g. native) app must record consent to the ToS version IT shipped\n * and displayed, for legal correctness — never backend-latest.\n *\n * Releasing a ToS-TEXT change ⇒ bump this constant + cut a new\n * @verbumia/feedback release (per the human/real-CI publish handoff).\n * The backend accepts any version in its acceptable set and records it.\n */\nexport const SDK_TOS_VERSION = \"2026-05-18\";\n","/**\n * Framework-agnostic Verbumia feedback client.\n *\n * Responsibilities:\n * - versioned-ToS acceptance -> scoped end-user JWT bootstrap\n * - transparent access-token refresh (rotating refresh token)\n * - fetch on-screen strings for the widget\n * - debounced + size-capped batched POST of ratings / suggestions,\n * mirroring the missing-handler transport contract (ltm 280):\n * best-effort, never throws into the host app render path.\n *\n * The React and React Native entry points are thin UI shells over this.\n */\n\nimport {\n type BatchResponse,\n type FeedbackAddonState,\n type FeedbackConfig,\n FeedbackError,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"./types\";\nimport { SDK_TOS_VERSION } from \"./tos\";\n\nconst SDK_LIB = \"@verbumia/feedback\";\nconst SDK_VER = \"0.2.0\";\n\ntype QueueItem =\n | { kind: \"rating\"; payload: RatingInput }\n | { kind: \"suggestion\"; payload: SuggestionInput };\n\nexport class FeedbackClient {\n private cfg: Required<\n Pick<FeedbackConfig, \"flushDebounceMs\" | \"maxBatch\">\n > &\n FeedbackConfig;\n private fetchImpl: typeof fetch;\n private tokens: TokenBundle | null = null;\n private queue: QueueItem[] = [];\n private timer: ReturnType<typeof setTimeout> | null = null;\n private bootstrapping: Promise<TokenBundle> | null = null;\n\n constructor(config: FeedbackConfig) {\n this.cfg = {\n flushDebounceMs: 1500,\n maxBatch: 20,\n ...config,\n };\n const f = config.fetchImpl ?? globalThis.fetch;\n if (!f) {\n throw new FeedbackError(\n \"no fetch implementation available; pass config.fetchImpl\",\n );\n }\n this.fetchImpl = f.bind(globalThis);\n }\n\n private base(): string {\n return this.cfg.apiBase.replace(/\\/+$/, \"\");\n }\n\n get endUserId(): string | undefined {\n return this.tokens?.end_user_id ?? this.cfg.endUserId;\n }\n\n get hasConsented(): boolean {\n return this.tokens !== null;\n }\n\n /** Alias of `hasConsented` exposed under the 0.2.7 naming used by the\n * `controller.hasAcceptedTos` getter (the SDK's built-in modal and a\n * host's external ToS page share the same persisted token bundle, so\n * both flip this boolean once a session is bootstrapped). */\n get hasAcceptedTos(): boolean {\n return this.tokens !== null;\n }\n\n /** Server-minted sessionId / grouping_key (from the token bundle).\n * Available only after `acceptTos()`; never client-generated. */\n get sessionId(): string | undefined {\n return this.tokens?.grouping_key;\n }\n\n /**\n * 0.2.7 — `GET /v1/projects/{projectId}/feedback-addon/state`.\n *\n * Auth selection (matches backend's dual-acceptance after task 836's\n * follow-up PR — see CONTRACT note in the response type):\n * - If `cfg.apiKey` is set → `Authorization: ApiKey <key>` (so the\n * plugin can fetch state at `setup()` time, before the user has\n * accepted ToS).\n * - Else if a user-session bundle exists → `Authorization: Bearer\n * <access_token>` (the deferred-after-acceptTos path).\n * - Else → throw `FeedbackError(\"addon state requires apiKey or\n * acceptTos\")`. The plugin's setup catches and surfaces a\n * console.warn the first time, then re-attempts on the\n * bundle-mint that follows `acceptTos()`.\n *\n * Best-effort: a transport error returns `null` instead of throwing,\n * so the controller's `isActive` falls back to whatever was last\n * known (or its initial value) without flipping the host's CTA into\n * an inconsistent state on a transient blip.\n */\n async getAddonState(): Promise<FeedbackAddonState | null> {\n let auth: string | undefined;\n if (this.cfg.apiKey) {\n auth = `ApiKey ${this.cfg.apiKey}`;\n } else if (this.tokens) {\n auth = `Bearer ${this.tokens.access_token}`;\n } else {\n throw new FeedbackError(\n \"addon state requires apiKey or acceptTos\",\n );\n }\n const url = `${this.base()}/v1/projects/${this.cfg.projectId}/feedback-addon/state`;\n try {\n const res = await this.fetchImpl(url, {\n method: \"GET\",\n headers: { Authorization: auth },\n });\n if (!res.ok) return null;\n return (await res.json()) as FeedbackAddonState;\n } catch {\n return null;\n }\n }\n\n /** BCP-47 language the widget is rating strings in. */\n get language(): string {\n return this.cfg.language;\n }\n\n /**\n * Re-target the widget's working language at runtime (#806 SeedSower\n * lang-change propagation). Subsequent `getStrings` / `rate` /\n * `suggest` use the new language without rebuilding the client. The\n * server-minted ToS bundle has no `locale` field, so a language flip\n * does NOT invalidate consent — only the next `?language=` query\n * needs updating. The i18n provider plugin (feedback ≥0.2.6)\n * subscribes to `@verbumia/react-i18next` ≥1.0.5's\n * `VerbumiaPluginContext.onLanguageChange` and calls this on every\n * runtime language change so the host never has to wire it manually.\n */\n setLanguage(lang: string): void {\n if (typeof lang !== \"string\" || lang.length === 0) return;\n if (lang === this.cfg.language) return;\n this.cfg = { ...this.cfg, language: lang };\n }\n\n /** ToS version the end user is asked to accept — the SDK's\n * build-time constant (task 616). NOT integrator/server set. */\n get tosVersion(): string {\n return SDK_TOS_VERSION;\n }\n\n /**\n * Accept the ToS and bootstrap a scoped token. Idempotent: a second call\n * returns the in-flight / existing bundle rather than re-accepting.\n */\n async acceptTos(): Promise<TokenBundle> {\n if (this.tokens) return this.tokens;\n if (this.bootstrapping) return this.bootstrapping;\n this.bootstrapping = (async () => {\n const res = await this.fetchImpl(`${this.base()}/v1/feedback/tos`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n // NO grouping_key: server-minted (task 599). It comes back in\n // the token bundle and is bound into the JWT server-side.\n project_id: this.cfg.projectId,\n end_user_id: this.cfg.endUserId,\n tos_version: SDK_TOS_VERSION,\n locale: this.cfg.locale,\n }),\n });\n if (!res.ok) throw await this.problem(res, \"tos acceptance failed\");\n this.tokens = (await res.json()) as TokenBundle;\n return this.tokens;\n })();\n try {\n return await this.bootstrapping;\n } finally {\n this.bootstrapping = null;\n }\n }\n\n private async refresh(): Promise<void> {\n if (!this.tokens) throw new FeedbackError(\"not consented\");\n const res = await this.fetchImpl(\n `${this.base()}/v1/feedback/token/refresh`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refresh_token: this.tokens.refresh_token }),\n },\n );\n if (!res.ok) {\n this.tokens = null; // force a fresh ToS step\n throw await this.problem(res, \"token refresh failed\");\n }\n this.tokens = (await res.json()) as TokenBundle;\n }\n\n /** Authenticated fetch with a single transparent refresh-on-401 retry.\n * When `cfg.autoAcceptTos === false` (the plugin's `tos: \"skip\"` opt-in\n * / 0.2.7) and no token has been minted yet, throws a `not consented`\n * error instead of silently calling `acceptTos()` — the host promises\n * to handle consent externally via `controller.acceptTos()`. */\n private async authed(\n path: string,\n init: RequestInit,\n retry = true,\n ): Promise<Response> {\n if (!this.tokens) {\n if (this.cfg.autoAcceptTos === false) {\n throw new FeedbackError(\"not consented\");\n }\n await this.acceptTos();\n }\n const res = await this.fetchImpl(`${this.base()}${path}`, {\n ...init,\n headers: {\n ...(init.headers ?? {}),\n Authorization: `Bearer ${this.tokens!.access_token}`,\n },\n });\n if (res.status === 401 && retry) {\n await this.refresh();\n return this.authed(path, init, false);\n }\n return res;\n }\n\n /** Strings rendered on the current view, with this end user's prior rating. */\n async getStrings(opts?: {\n keys?: Array<{ namespace: string; key: string }>;\n namespace?: string;\n limit?: number;\n }): Promise<StringsResponse> {\n // #806 SeedSower P1 (Hermes URLSearchParams polyfill): React Native's\n // bundled URLSearchParams implements ONLY the constructor + toString().\n // `.set` / `.get` / `.append` / `.delete` / `.has` / `.entries` /\n // `.forEach` / `.keys` / `.values` / the iterator all throw on Hermes\n // (documented RN limitation since ~0.50, refused for bundle-size).\n // Build a plain Record<string, string> first, then pass to the\n // constructor — that variant is supported on Node + browser + Hermes\n // + JSC, and produces the same query string. NO mutation after\n // construction anywhere in the SDK.\n const params: Record<string, string> = { language: this.cfg.language };\n if (opts?.keys?.length) {\n params.keys = opts.keys.map((k) => `${k.namespace}:${k.key}`).join(\",\");\n }\n if (opts?.namespace) params.namespace = opts.namespace;\n if (opts?.limit) params.limit = String(opts.limit);\n const qs = new URLSearchParams(params);\n const res = await this.authed(`/v1/feedback/strings?${qs.toString()}`, {\n method: \"GET\",\n });\n if (!res.ok) throw await this.problem(res, \"failed to load strings\");\n return (await res.json()) as StringsResponse;\n }\n\n /** Queue a rating; flushed on debounce or when the batch fills. */\n rate(payload: RatingInput): void {\n this.enqueue({ kind: \"rating\", payload });\n }\n\n /** Queue a suggestion; flushed on debounce or when the batch fills. */\n suggest(payload: SuggestionInput): void {\n this.enqueue({ kind: \"suggestion\", payload });\n }\n\n private enqueue(item: QueueItem): void {\n this.queue.push(item);\n if (this.queue.length >= this.cfg.maxBatch) {\n void this.flush();\n return;\n }\n if (this.timer) clearTimeout(this.timer);\n this.timer = setTimeout(() => void this.flush(), this.cfg.flushDebounceMs);\n }\n\n /**\n * Flush queued items. Best-effort: a transport/auth failure re-queues the\n * batch once and swallows the error so the host app never sees a throw.\n */\n async flush(): Promise<void> {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n if (!this.queue.length) return;\n const batch = this.queue;\n this.queue = [];\n const ratings = batch\n .filter((b) => b.kind === \"rating\")\n .map((b) => b.payload as RatingInput);\n const suggestions = batch\n .filter((b) => b.kind === \"suggestion\")\n .map((b) => b.payload as SuggestionInput);\n try {\n if (ratings.length) {\n await this.postBatch(\"/v1/feedback/ratings\", { ratings });\n }\n if (suggestions.length) {\n await this.postBatch(\"/v1/feedback/suggestions\", { suggestions });\n }\n } catch {\n // Re-queue once; feedback must never break the host app.\n this.queue.unshift(...batch);\n }\n }\n\n private async postBatch(\n path: string,\n body: unknown,\n ): Promise<BatchResponse> {\n const res = await this.authed(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-SDK\": `${SDK_LIB}@${SDK_VER}` },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw await this.problem(res, \"batch post failed\");\n return (await res.json()) as BatchResponse;\n }\n\n private async problem(res: Response, fallback: string): Promise<FeedbackError> {\n let code: string | undefined;\n let detail = fallback;\n try {\n const body = (await res.json()) as { code?: string; detail?: string };\n code = body.code;\n if (body.detail) detail = body.detail;\n } catch {\n /* non-JSON body */\n }\n return new FeedbackError(detail, res.status, code);\n }\n}\n","import { useCallback, useEffect, useState } from \"react\";\n\nimport type { FeedbackClient } from \"../core/client\";\nimport { resolveKeys } from \"../core/keys\";\nimport type { DeclaredKey, FeedbackString } from \"../core/types\";\n\nconst C = {\n bg: \"#0b0f0e\",\n panel: \"#111714\",\n border: \"#1f2a25\",\n text: \"#e7f5ef\",\n dim: \"#8aa79b\",\n emerald: \"#10b981\",\n emeraldSoft: \"#34d399\",\n};\n\nexport function FeedbackPanel(props: {\n client: FeedbackClient;\n keys?: DeclaredKey[];\n namespace?: string | string[];\n /** 0.2.7 — `\"skip\"` removes the built-in ToS step entirely; the host\n * owns consent (via `controller.acceptTos()`). When the user has not\n * yet accepted, the strings fetch surfaces a 401-derived error in the\n * existing error row. */\n tos?: \"modal\" | \"skip\";\n onClose: () => void;\n}) {\n const { client, keys, namespace, tos = \"modal\", onClose } = props;\n // `tos: \"skip\"` short-circuits the local consent state so the panel\n // renders the strings section directly. The client refuses to\n // auto-acceptTos (autoAcceptTos:false on the plugin side), so a\n // missing token surfaces as the existing 401 error row.\n const [consented, setConsented] = useState(\n tos === \"skip\" ? true : client.hasConsented,\n );\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [strings, setStrings] = useState<FeedbackString[]>([]);\n\n const loadStrings = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n // On-screen scoping is NORMATIVE (spec ltm 373, CONTRACT v5): show\n // ONLY rendered keys. Empty -> \"no strings on this view\"; never\n // fall back to fetching the whole project.\n const resolved = resolveKeys(keys, namespace);\n if (!resolved.length) {\n // #806 SeedSower P1 — when the panel opens with NO explicit\n // `keys` prop (i.e. it depended on the i18n SDK's on-screen\n // registry), an empty resolve almost always means the host's\n // `useTranslation` imports went to `react-i18next` instead of\n // `@verbumia/react-i18next`. Either way, the keys never fed the\n // registry. Log a single, actionable hint so the integrator\n // doesn't waste a debug cycle on \"the widget is broken\".\n if (!keys || keys.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[verbumia/feedback] registry empty — are your useTranslation imports coming from @verbumia/react-i18next?\",\n );\n }\n setStrings([]);\n return;\n }\n const res = await client.getStrings({ keys: resolved });\n setStrings(res.strings);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Failed to load strings\");\n } finally {\n setBusy(false);\n }\n }, [client, keys, namespace]);\n\n useEffect(() => {\n if (consented) void loadStrings();\n }, [consented, loadStrings]);\n\n const accept = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n await client.acceptTos();\n setConsented(true);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Could not accept the terms\");\n } finally {\n setBusy(false);\n }\n }, [client]);\n\n return (\n <div\n role=\"dialog\"\n aria-label=\"Translation feedback\"\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483600,\n background: \"rgba(0,0,0,.55)\",\n display: \"flex\",\n justifyContent: \"flex-end\",\n }}\n onClick={onClose}\n >\n <div\n onClick={(e) => e.stopPropagation()}\n style={{\n width: \"min(420px, 100%)\",\n height: \"100%\",\n background: C.bg,\n color: C.text,\n borderLeft: `1px solid ${C.border}`,\n display: \"flex\",\n flexDirection: \"column\",\n fontFamily: \"system-ui, sans-serif\",\n }}\n >\n <header\n style={{\n padding: \"16px 18px\",\n borderBottom: `1px solid ${C.border}`,\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n }}\n >\n <strong style={{ color: C.emeraldSoft }}>Translation feedback</strong>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n style={{\n background: \"transparent\",\n color: C.dim,\n border: \"none\",\n fontSize: 20,\n cursor: \"pointer\",\n }}\n >\n ×\n </button>\n </header>\n\n <div style={{ padding: 18, overflowY: \"auto\", flex: 1 }}>\n {error && (\n <p style={{ color: \"#f87171\", fontSize: 13 }}>{error}</p>\n )}\n\n {!consented ? (\n <ConsentStep\n version={client.tosVersion}\n busy={busy}\n onAccept={accept}\n />\n ) : busy && !strings.length ? (\n <p style={{ color: C.dim }}>Loading…</p>\n ) : !strings.length ? (\n <p style={{ color: C.dim }}>No strings to review on this view.</p>\n ) : (\n strings.map((s) => (\n <StringRow key={`${s.namespace}:${s.key}`} s={s} client={client} />\n ))\n )}\n </div>\n <footer\n style={{\n padding: \"10px 18px\",\n borderTop: `1px solid ${C.border}`,\n color: C.dim,\n fontSize: 11,\n }}\n >\n Powered by Verbumia\n </footer>\n </div>\n </div>\n );\n}\n\nfunction ConsentStep(props: {\n version?: string;\n busy: boolean;\n onAccept: () => void;\n}) {\n return (\n <div>\n <p style={{ lineHeight: 1.5, fontSize: 14 }}>\n Help improve the translations in this app. Your ratings and\n suggestions are sent to the app owner via Verbumia. Don’t submit\n personal or sensitive information.\n </p>\n <button\n type=\"button\"\n disabled={props.busy}\n onClick={props.onAccept}\n style={{\n marginTop: 12,\n width: \"100%\",\n background: C.emerald,\n color: \"#03110c\",\n border: \"none\",\n borderRadius: 8,\n padding: \"12px 14px\",\n fontWeight: 700,\n cursor: props.busy ? \"default\" : \"pointer\",\n opacity: props.busy ? 0.6 : 1,\n }}\n >\n {props.busy\n ? \"…\"\n : `I accept the terms${props.version ? ` (v${props.version})` : \"\"}`}\n </button>\n </div>\n );\n}\n\nfunction StringRow(props: { s: FeedbackString; client: FeedbackClient }) {\n const { s, client } = props;\n const [mine, setMine] = useState<number | null>(s.my_rating);\n const [showSuggest, setShowSuggest] = useState(false);\n const [text, setText] = useState(\"\");\n const [sent, setSent] = useState(false);\n\n const rate = (stars: number) => {\n setMine(stars);\n client.rate({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n stars,\n });\n };\n\n const submitSuggestion = () => {\n if (!text.trim()) return;\n client.suggest({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n suggested_text: text.trim(),\n });\n setSent(true);\n setShowSuggest(false);\n setText(\"\");\n };\n\n return (\n <div\n style={{\n background: C.panel,\n border: `1px solid ${C.border}`,\n borderRadius: 10,\n padding: 12,\n marginBottom: 10,\n }}\n >\n <div style={{ fontSize: 12, color: C.dim }}>\n {s.namespace} · {s.key}\n </div>\n <div style={{ margin: \"6px 0 10px\", fontSize: 15 }}>{s.value}</div>\n <div style={{ display: \"flex\", gap: 4 }}>\n {[1, 2, 3, 4, 5].map((n) => (\n <button\n key={n}\n type=\"button\"\n aria-label={`${n} star${n > 1 ? \"s\" : \"\"}`}\n onClick={() => rate(n)}\n style={{\n background: \"transparent\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: 20,\n color: mine && n <= mine ? C.emeraldSoft : C.border,\n }}\n >\n ★\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowSuggest((v) => !v)}\n style={{\n marginLeft: \"auto\",\n background: \"transparent\",\n color: C.emeraldSoft,\n border: `1px solid ${C.border}`,\n borderRadius: 6,\n padding: \"4px 10px\",\n fontSize: 12,\n cursor: \"pointer\",\n }}\n >\n {sent ? \"Suggested ✓\" : \"Suggest\"}\n </button>\n </div>\n {showSuggest && (\n <div style={{ marginTop: 10 }}>\n <textarea\n value={text}\n onChange={(e) => setText(e.target.value)}\n rows={3}\n placeholder=\"Your suggested translation…\"\n style={{\n width: \"100%\",\n background: C.bg,\n color: C.text,\n border: `1px solid ${C.border}`,\n borderRadius: 6,\n padding: 8,\n fontSize: 14,\n resize: \"vertical\",\n }}\n />\n <button\n type=\"button\"\n onClick={submitSuggestion}\n style={{\n marginTop: 6,\n background: C.emerald,\n color: \"#03110c\",\n border: \"none\",\n borderRadius: 6,\n padding: \"8px 12px\",\n fontWeight: 700,\n cursor: \"pointer\",\n }}\n >\n Send suggestion\n </button>\n </div>\n )}\n </div>\n );\n}\n","/**\n * On-screen key discovery.\n *\n * Preferred source: the `@verbumia/*-i18n` SDK exposes a lightweight key\n * registry of keys it has rendered. When that registry is present on the\n * global (the i18n SDK publishes `globalThis.__verbumia_key_registry__`),\n * we read the keys touched on the current view. Otherwise the host app\n * passes an explicit `keys` list — the always-available fallback.\n *\n * The registry contract is intentionally tiny so any framework port of the\n * i18n SDK can implement it without depending on this package.\n */\n\nimport type { DeclaredKey } from \"./types\";\n\nconst REGISTRY_GLOBAL = \"__verbumia_key_registry__\";\n\ninterface KeyRegistry {\n /** Returns the keys rendered since the last `reset()` (or ever). */\n snapshot(): DeclaredKey[];\n reset?(): void;\n}\n\nfunction getRegistry(): KeyRegistry | null {\n const g = globalThis as Record<string, unknown>;\n const reg = g[REGISTRY_GLOBAL];\n if (\n reg &&\n typeof (reg as KeyRegistry).snapshot === \"function\"\n ) {\n return reg as KeyRegistry;\n }\n return null;\n}\n\n/** True when a compatible `@verbumia/*-i18n` registry is detectable. */\nexport function hasKeyRegistry(): boolean {\n return getRegistry() !== null;\n}\n\n/**\n * Resolve the on-screen keys: explicit `keys` prop always wins (it is the\n * customer's authoritative declaration); otherwise fall back to the i18n\n * registry snapshot; otherwise an empty list (widget shows \"no strings\").\n *\n * Optional `namespace` filter (task 618, CONTRACT v6 — ADDITIVE): when\n * set (a single namespace or a list), the resolved set is narrowed to\n * keys whose `namespace` is in the filter. The filter is applied AFTER\n * resolution, so it composes with v5 rendered-scoping as\n * `rendered ∩ namespace`. UNSET / empty ⇒ unchanged v5 behaviour (all\n * resolved keys). Backward-compatible: `resolveKeys(keys)` is unaffected.\n */\nexport function resolveKeys(\n explicit?: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const base =\n explicit && explicit.length\n ? dedupe(explicit)\n : getRegistry()\n ? dedupe(getRegistry()!.snapshot())\n : [];\n return filterByNamespace(base, namespace);\n}\n\n/** Narrow keys to those whose namespace is in `namespace`. An undefined\n * filter, an empty string, or an empty list means \"no filter\" (the\n * additive default — identical to v5). Exported so non-panel callers\n * (e.g. a custom /core integration) can apply the same semantics. */\nexport function filterByNamespace(\n keys: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const list = (Array.isArray(namespace) ? namespace : namespace ? [namespace] : [])\n .map((n) => n.trim())\n .filter(Boolean);\n if (!list.length) return keys;\n const allow = new Set(list);\n return keys.filter((k) => allow.has(k.namespace));\n}\n\nfunction dedupe(keys: DeclaredKey[]): DeclaredKey[] {\n const seen = new Set<string>();\n const out: DeclaredKey[] = [];\n for (const k of keys) {\n const id = `${k.namespace}:${k.key}`;\n if (!seen.has(id)) {\n seen.add(id);\n out.push(k);\n }\n }\n return out;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}