anton-bakker-feedback 0.3.3 → 0.3.4

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.
@@ -88,6 +88,23 @@ const en = {
88
88
  "feedback.form.contentPlaceholder": "Describe the issue or suggestion in detail...",
89
89
  "feedback.form.typeLabel": "Type",
90
90
  "feedback.form.typePlaceholder": "Select a type",
91
+ // Confirm (review) form
92
+ "feedback.confirm.heading": "Review your feedback",
93
+ "feedback.confirm.titleLabel": "Title",
94
+ "feedback.confirm.descriptionLabel": "Description",
95
+ "feedback.confirm.typeLabel": "Type",
96
+ "feedback.confirm.scopeLabel": "Scope",
97
+ "feedback.confirm.sizeLabel": "Size",
98
+ "feedback.confirm.duplicateWarning": "Similar feedback already exists:",
99
+ "feedback.confirm.scopePlaceholder": "Select scope (optional)",
100
+ "feedback.confirm.scope.isolated": "Isolated",
101
+ "feedback.confirm.scope.crossCutting": "Cross-cutting",
102
+ "feedback.confirm.scope.systemic": "Systemic",
103
+ "feedback.confirm.sizePlaceholder": "Select size (optional)",
104
+ "feedback.confirm.size.trivial": "Trivial",
105
+ "feedback.confirm.size.small": "Small",
106
+ "feedback.confirm.size.medium": "Medium",
107
+ "feedback.confirm.size.large": "Large",
91
108
  // Dialogue states
92
109
  "feedback.dialogue.opening": "Thank you for your feedback. I have a few questions to help us understand better.",
93
110
  "feedback.dialogue.thinking": "Thinking...",
@@ -130,6 +147,23 @@ const nl = {
130
147
  "feedback.form.contentPlaceholder": "Beschrijf het probleem of de suggestie in detail...",
131
148
  "feedback.form.typeLabel": "Type",
132
149
  "feedback.form.typePlaceholder": "Selecteer een type",
150
+ // Confirm (review) form
151
+ "feedback.confirm.heading": "Controleer uw feedback",
152
+ "feedback.confirm.titleLabel": "Titel",
153
+ "feedback.confirm.descriptionLabel": "Omschrijving",
154
+ "feedback.confirm.typeLabel": "Type",
155
+ "feedback.confirm.scopeLabel": "Reikwijdte",
156
+ "feedback.confirm.sizeLabel": "Omvang",
157
+ "feedback.confirm.duplicateWarning": "Er bestaat al vergelijkbare feedback:",
158
+ "feedback.confirm.scopePlaceholder": "Selecteer reikwijdte (optioneel)",
159
+ "feedback.confirm.scope.isolated": "Geïsoleerd",
160
+ "feedback.confirm.scope.crossCutting": "Overkoepelend",
161
+ "feedback.confirm.scope.systemic": "Systemisch",
162
+ "feedback.confirm.sizePlaceholder": "Selecteer omvang (optioneel)",
163
+ "feedback.confirm.size.trivial": "Triviaal",
164
+ "feedback.confirm.size.small": "Klein",
165
+ "feedback.confirm.size.medium": "Middel",
166
+ "feedback.confirm.size.large": "Groot",
133
167
  // Dialogue states
134
168
  "feedback.dialogue.opening": "Bedankt voor uw feedback. Ik heb een paar vragen om dit beter te begrijpen.",
135
169
  "feedback.dialogue.thinking": "Aan het nadenken...",
@@ -207,4 +241,4 @@ export {
207
241
  t,
208
242
  validateConfig as v
209
243
  };
210
- //# sourceMappingURL=i18n-Du-or3T4.js.map
244
+ //# sourceMappingURL=i18n-Cba0xHU-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n-Cba0xHU-.js","sources":["../../src/core/config.ts","../../src/core/locales/en.ts","../../src/core/locales/nl.ts","../../src/core/i18n.ts"],"sourcesContent":["/**\n * Configuration schema and validation for the anton-bakker-feedback package.\n */\n\nimport type { FeedbackEnvironment } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Config Interface\n// ─────────────────────────────────────────────────────────────────\n\n/** Full configuration object for the feedback package */\nexport interface FeedbackConfig {\n /** Required — alphanumeric, hyphens, underscores only */\n appName: string;\n version?: string;\n /** Defaults to 'production' if absent */\n environment?: FeedbackEnvironment;\n\n transport: {\n /** POST endpoint — app provides its own route */\n submitUrl: string;\n /** If absent → dialogue disabled, form shown */\n dialogueUrl?: string;\n /** If absent → screenshots disabled */\n screenshotUploadUrl?: string;\n };\n\n dialogue?: {\n /** Default 3; min 1, max 10 */\n maxTurns?: number;\n /** Default 30_000 */\n timeoutMs?: number;\n };\n\n screenshots?: {\n /** Default true if screenshotUploadUrl set */\n enabled?: boolean;\n /** Default 5_242_880 (5 MB) */\n maxSizeBytes?: number;\n };\n\n ui?: {\n /** Default 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Default 'auto' */\n theme?: 'light' | 'dark' | 'auto';\n };\n\n observability?: {\n /** Default true */\n enabled?: boolean;\n /** Default 'Feedback/Service' */\n namespace?: string;\n /** Default ['x-trace-id', 'x-request-id'] */\n traceIdHeaders?: string[];\n };\n\n i18n?: {\n /** Default 'en' */\n locale?: string;\n customTranslations?: Record<string, Record<string, string>>;\n };\n\n signOff?: {\n /** Default 7; 0 = manual only */\n autoConfirmDays?: number;\n /** Default 3 */\n reminderDays?: number;\n };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Resolved Config — all fields have values, no undefined\n// ─────────────────────────────────────────────────────────────────\n\n/** Fully-resolved config with all defaults applied — no undefined fields */\nexport interface ResolvedFeedbackConfig {\n appName: string;\n version: string;\n environment: FeedbackEnvironment;\n\n transport: {\n submitUrl: string;\n dialogueUrl: string | undefined;\n screenshotUploadUrl: string | undefined;\n };\n\n dialogue: {\n maxTurns: number;\n timeoutMs: number;\n };\n\n screenshots: {\n enabled: boolean;\n maxSizeBytes: number;\n };\n\n ui: {\n position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n theme: 'light' | 'dark' | 'auto';\n };\n\n observability: {\n enabled: boolean;\n namespace: string;\n traceIdHeaders: string[];\n };\n\n i18n: {\n locale: string;\n customTranslations: Record<string, Record<string, string>>;\n };\n\n signOff: {\n autoConfirmDays: number;\n reminderDays: number;\n };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Validation\n// ─────────────────────────────────────────────────────────────────\n\nconst APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;\nconst URL_PATTERN = /^https?:\\/\\/.+/;\n\n/** Validation result with structured errors */\nexport interface ConfigValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validates a FeedbackConfig and returns structured errors.\n * Returns { valid: false, errors: [...] } with actionable messages.\n */\nexport function validateConfig(config: Partial<FeedbackConfig>): ConfigValidationResult {\n const errors: string[] = [];\n\n // Validate appName\n if (!config.appName || config.appName.trim() === '') {\n errors.push('appName is required and must not be empty');\n } else if (!APP_NAME_PATTERN.test(config.appName)) {\n errors.push(\n `appName \"${config.appName}\" is invalid — must match /^[a-zA-Z0-9_-]+$/ (no spaces, slashes, or special characters)`,\n );\n }\n\n // Validate transport.submitUrl\n if (!config.transport?.submitUrl || config.transport.submitUrl.trim() === '') {\n errors.push('transport.submitUrl is required and must not be empty');\n } else if (!URL_PATTERN.test(config.transport.submitUrl)) {\n errors.push(\n `transport.submitUrl \"${config.transport.submitUrl}\" must be a valid URL starting with http:// or https://`,\n );\n }\n\n // Validate dialogue.maxTurns\n if (config.dialogue?.maxTurns !== undefined) {\n const turns = config.dialogue.maxTurns;\n if (!Number.isInteger(turns) || turns < 1 || turns > 10) {\n errors.push(\n `dialogue.maxTurns must be an integer between 1 and 10 (got ${turns})`,\n );\n }\n }\n\n // Validate screenshots.maxSizeBytes\n if (config.screenshots?.maxSizeBytes !== undefined) {\n const size = config.screenshots.maxSizeBytes;\n if (!Number.isFinite(size) || size <= 0) {\n errors.push(`screenshots.maxSizeBytes must be a positive number (got ${size})`);\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n\n/**\n * Merges user-supplied partial config with defaults.\n * Returns a fully-resolved config with no undefined fields.\n */\nexport function resolveConfig(config: FeedbackConfig): ResolvedFeedbackConfig {\n const screenshotsEnabled =\n config.screenshots?.enabled !== undefined\n ? config.screenshots.enabled\n : !!config.transport.screenshotUploadUrl;\n\n return {\n appName: config.appName,\n version: config.version ?? '0.0.0',\n environment: config.environment ?? 'production',\n\n transport: {\n submitUrl: config.transport.submitUrl,\n dialogueUrl: config.transport.dialogueUrl,\n screenshotUploadUrl: config.transport.screenshotUploadUrl,\n },\n\n dialogue: {\n maxTurns: config.dialogue?.maxTurns ?? 3,\n timeoutMs: config.dialogue?.timeoutMs ?? 30_000,\n },\n\n screenshots: {\n enabled: screenshotsEnabled,\n maxSizeBytes: config.screenshots?.maxSizeBytes ?? 5_242_880,\n },\n\n ui: {\n position: config.ui?.position ?? 'bottom-right',\n theme: config.ui?.theme ?? 'auto',\n },\n\n observability: {\n enabled: config.observability?.enabled ?? true,\n namespace: config.observability?.namespace ?? 'Feedback/Service',\n traceIdHeaders: config.observability?.traceIdHeaders ?? [\n 'x-trace-id',\n 'x-request-id',\n ],\n },\n\n i18n: {\n locale: config.i18n?.locale ?? 'en',\n customTranslations: config.i18n?.customTranslations ?? {},\n },\n\n signOff: {\n autoConfirmDays: config.signOff?.autoConfirmDays ?? 7,\n reminderDays: config.signOff?.reminderDays ?? 3,\n },\n };\n}\n","/**\n * English (en) translations for the feedback package.\n * Key format: feedback.{component}.{element}\n */\nexport const en: Record<string, string> = {\n // Button labels\n 'feedback.button.open': 'Feedback',\n 'feedback.button.close': 'Close',\n 'feedback.button.screenshot': 'Add screenshot',\n 'feedback.button.removeScreenshot': 'Remove screenshot',\n 'feedback.button.submit': 'Submit feedback',\n\n // Form labels\n 'feedback.form.contentLabel': 'What would you like to share?',\n 'feedback.form.contentPlaceholder': 'Describe the issue or suggestion in detail...',\n 'feedback.form.typeLabel': 'Type',\n 'feedback.form.typePlaceholder': 'Select a type',\n\n // Confirm (review) form\n 'feedback.confirm.heading': 'Review your feedback',\n 'feedback.confirm.titleLabel': 'Title',\n 'feedback.confirm.descriptionLabel': 'Description',\n 'feedback.confirm.typeLabel': 'Type',\n 'feedback.confirm.scopeLabel': 'Scope',\n 'feedback.confirm.sizeLabel': 'Size',\n 'feedback.confirm.duplicateWarning': 'Similar feedback already exists:',\n 'feedback.confirm.scopePlaceholder': 'Select scope (optional)',\n 'feedback.confirm.scope.isolated': 'Isolated',\n 'feedback.confirm.scope.crossCutting': 'Cross-cutting',\n 'feedback.confirm.scope.systemic': 'Systemic',\n 'feedback.confirm.sizePlaceholder': 'Select size (optional)',\n 'feedback.confirm.size.trivial': 'Trivial',\n 'feedback.confirm.size.small': 'Small',\n 'feedback.confirm.size.medium': 'Medium',\n 'feedback.confirm.size.large': 'Large',\n\n // Dialogue states\n 'feedback.dialogue.opening': 'Thank you for your feedback. I have a few questions to help us understand better.',\n 'feedback.dialogue.thinking': 'Thinking...',\n 'feedback.dialogue.error': 'Sorry, something went wrong. Please try again.',\n 'feedback.dialogue.complete': 'Thank you! Your feedback has been recorded.',\n 'feedback.dialogue.turnPrompt': 'Your response:',\n\n // Feedback types\n 'feedback.types.bug': 'Bug report',\n 'feedback.types.feature': 'Feature request',\n 'feedback.types.ux': 'UX improvement',\n 'feedback.types.other': 'Other',\n\n // Status messages\n 'feedback.status.submitting': 'Submitting your feedback...',\n 'feedback.status.success': 'Feedback submitted successfully!',\n 'feedback.status.error': 'Failed to submit feedback. Please try again.',\n 'feedback.status.retry': 'Retry',\n 'feedback.status.duplicate': 'This feedback has already been submitted.',\n\n // Toast messages\n 'feedback.toast.screenshotAdded': 'Screenshot attached',\n 'feedback.toast.screenshotRemoved': 'Screenshot removed',\n 'feedback.toast.screenshotError': 'Failed to capture screenshot',\n\n // Sign-off messages\n 'feedback.signoff.confirmLabel': 'Mark as resolved',\n 'feedback.signoff.reopenLabel': 'Reopen',\n 'feedback.signoff.autoConfirmNotice':\n 'This issue will be marked resolved in {{days}} days unless you reopen it.',\n\n // Correction messages\n 'feedback.correction.editLabel': 'Edit',\n 'feedback.correction.reasonLabel': 'Reason for change',\n 'feedback.correction.saveLabel': 'Save changes',\n};\n","/**\n * Dutch (nl) translations for the feedback package.\n * Key format: feedback.{component}.{element}\n */\nexport const nl: Record<string, string> = {\n // Button labels\n 'feedback.button.open': 'Feedback',\n 'feedback.button.close': 'Sluiten',\n 'feedback.button.screenshot': 'Screenshot toevoegen',\n 'feedback.button.removeScreenshot': 'Screenshot verwijderen',\n 'feedback.button.submit': 'Feedback versturen',\n\n // Form labels\n 'feedback.form.contentLabel': 'Wat wilt u delen?',\n 'feedback.form.contentPlaceholder': 'Beschrijf het probleem of de suggestie in detail...',\n 'feedback.form.typeLabel': 'Type',\n 'feedback.form.typePlaceholder': 'Selecteer een type',\n\n // Confirm (review) form\n 'feedback.confirm.heading': 'Controleer uw feedback',\n 'feedback.confirm.titleLabel': 'Titel',\n 'feedback.confirm.descriptionLabel': 'Omschrijving',\n 'feedback.confirm.typeLabel': 'Type',\n 'feedback.confirm.scopeLabel': 'Reikwijdte',\n 'feedback.confirm.sizeLabel': 'Omvang',\n 'feedback.confirm.duplicateWarning': 'Er bestaat al vergelijkbare feedback:',\n 'feedback.confirm.scopePlaceholder': 'Selecteer reikwijdte (optioneel)',\n 'feedback.confirm.scope.isolated': 'Geïsoleerd',\n 'feedback.confirm.scope.crossCutting': 'Overkoepelend',\n 'feedback.confirm.scope.systemic': 'Systemisch',\n 'feedback.confirm.sizePlaceholder': 'Selecteer omvang (optioneel)',\n 'feedback.confirm.size.trivial': 'Triviaal',\n 'feedback.confirm.size.small': 'Klein',\n 'feedback.confirm.size.medium': 'Middel',\n 'feedback.confirm.size.large': 'Groot',\n\n // Dialogue states\n 'feedback.dialogue.opening': 'Bedankt voor uw feedback. Ik heb een paar vragen om dit beter te begrijpen.',\n 'feedback.dialogue.thinking': 'Aan het nadenken...',\n 'feedback.dialogue.error': 'Er is iets misgegaan. Probeer het opnieuw.',\n 'feedback.dialogue.complete': 'Bedankt! Uw feedback is geregistreerd.',\n 'feedback.dialogue.turnPrompt': 'Uw antwoord:',\n\n // Feedback types\n 'feedback.types.bug': 'Foutmelding',\n 'feedback.types.feature': 'Functiewens',\n 'feedback.types.ux': 'UX-verbetering',\n 'feedback.types.other': 'Overig',\n\n // Status messages\n 'feedback.status.submitting': 'Feedback verzenden...',\n 'feedback.status.success': 'Feedback succesvol verzonden!',\n 'feedback.status.error': 'Verzenden mislukt. Probeer het opnieuw.',\n 'feedback.status.retry': 'Opnieuw proberen',\n 'feedback.status.duplicate': 'Deze feedback is al ingediend.',\n\n // Toast messages\n 'feedback.toast.screenshotAdded': 'Screenshot bijgevoegd',\n 'feedback.toast.screenshotRemoved': 'Screenshot verwijderd',\n 'feedback.toast.screenshotError': 'Screenshot maken mislukt',\n\n // Sign-off messages\n 'feedback.signoff.confirmLabel': 'Markeer als opgelost',\n 'feedback.signoff.reopenLabel': 'Heropenen',\n 'feedback.signoff.autoConfirmNotice':\n 'Dit probleem wordt als opgelost gemarkeerd over {{days}} dagen, tenzij u het heropent.',\n\n // Correction messages\n 'feedback.correction.editLabel': 'Bewerken',\n 'feedback.correction.reasonLabel': 'Reden voor wijziging',\n 'feedback.correction.saveLabel': 'Wijzigingen opslaan',\n};\n","/**\n * Internationalisation utilities for the feedback package.\n *\n * t() falls back gracefully:\n * - Unknown locale → English\n * - Unknown key → returns the key itself (never undefined)\n *\n * Template interpolation: {{param}} → replaced with params values\n */\n\nimport { en } from './locales/en.js';\nimport { nl } from './locales/nl.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────\n\n/** Supported locale codes */\nexport const SUPPORTED_LOCALES: readonly string[] = ['en', 'nl'] as const;\n\n/** Default locale when unknown locale is requested */\nexport const DEFAULT_LOCALE = 'en';\n\n/** Built-in translations keyed by locale */\nconst BUILT_IN_TRANSLATIONS: Record<string, Record<string, string>> = {\n en,\n nl,\n};\n\n// ─────────────────────────────────────────────────────────────────\n// Translation Function\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Looks up a translation key.\n *\n * Fallback chain:\n * 1. customTranslations[locale][key]\n * 2. BUILT_IN_TRANSLATIONS[locale][key]\n * 3. BUILT_IN_TRANSLATIONS['en'][key]\n * 4. key itself (never returns undefined or throws)\n *\n * Template interpolation: {{days}} → params.days (if params provided)\n *\n * @param key - Dot-notation key e.g. 'feedback.button.open'\n * @param locale - BCP 47 locale code e.g. 'en', 'nl'\n * @param customTranslations - Optional custom translations (overrides built-in)\n * @param params - Optional template parameters e.g. { days: 7 }\n */\nexport function t(\n key: string,\n locale: string,\n customTranslations?: Record<string, Record<string, string>>,\n params?: Record<string, string | number>,\n): string {\n // 1. Check custom translations for this locale\n const customForLocale = customTranslations?.[locale];\n if (customForLocale?.[key] !== undefined) {\n return interpolate(customForLocale[key], params);\n }\n\n // 2. Check custom translations for English fallback\n const customEn = customTranslations?.['en'];\n if (locale !== 'en' && customEn?.[key] !== undefined) {\n // Only use English custom if locale not found\n }\n\n // 3. Check built-in translations for this locale\n const builtInForLocale = BUILT_IN_TRANSLATIONS[locale];\n if (builtInForLocale?.[key] !== undefined) {\n return interpolate(builtInForLocale[key] as string, params);\n }\n\n // 4. Fall back to English built-in\n const englishBuiltIn = BUILT_IN_TRANSLATIONS['en'];\n if (englishBuiltIn?.[key] !== undefined) {\n return interpolate(englishBuiltIn[key] as string, params);\n }\n\n // 5. Fall back to English custom (if provided)\n if (customEn?.[key] !== undefined) {\n return interpolate(customEn[key] as string, params);\n }\n\n // 6. Return the key itself — never undefined\n return key;\n}\n\n/**\n * Replaces {{param}} placeholders with values from params.\n */\nfunction interpolate(\n template: string,\n params?: Record<string, string | number>,\n): string {\n if (!params) return template;\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_match, name: string) => {\n const value = params[name];\n return value !== undefined ? String(value) : `{{${name}}}`;\n });\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Locale Detection\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Returns the locale to use.\n * If preferredLocale is a supported locale → return it.\n * Otherwise → return DEFAULT_LOCALE ('en').\n *\n * @param preferredLocale - BCP 47 locale code or undefined\n */\nexport function detectLocale(preferredLocale?: string): string {\n if (preferredLocale && SUPPORTED_LOCALES.includes(preferredLocale)) {\n return preferredLocale;\n }\n return DEFAULT_LOCALE;\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/core/locales/en.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiDrC,CAAC"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/core/locales/en.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmErC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"nl.d.ts","sourceRoot":"","sources":["../../../src/core/locales/nl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiDrC,CAAC"}
1
+ {"version":3,"file":"nl.d.ts","sourceRoot":"","sources":["../../../src/core/locales/nl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmErC,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D, S, d, r, t, v } from "./chunks/i18n-Du-or3T4.js";
1
+ import { D, S, d, r, t, v } from "./chunks/i18n-Cba0xHU-.js";
2
2
  import { e, i, r as r2, a, w } from "./chunks/observability-CfwHKObh.js";
3
3
  const BASE_PRIORITY = {
4
4
  bug: 60,
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import React__default, { createContext, lazy, useMemo, useReducer, useEffect, useCallback, Suspense, useContext, useState, useRef } from "react";
3
- import { v as validateConfig, r as resolveConfig, d as detectLocale, t } from "./chunks/i18n-Du-or3T4.js";
3
+ import { v as validateConfig, r as resolveConfig, d as detectLocale, t } from "./chunks/i18n-Cba0xHU-.js";
4
4
  import { c } from "./chunks/submitResolver-DsfkrUHY.js";
5
5
  const FeedbackContext = createContext(null);
6
6
  FeedbackContext.displayName = "FeedbackContext";
@@ -975,9 +975,9 @@ function FeedbackConfirm({ className }) {
975
975
  "aria-label": "Submit feedback",
976
976
  noValidate: true,
977
977
  children: [
978
- /* @__PURE__ */ jsx("h2", { id: "feedback-confirm-title", className: "feedback-confirm__title", children: "Review your feedback" }),
978
+ /* @__PURE__ */ jsx("h2", { id: "feedback-confirm-title", className: "feedback-confirm__title", children: t("feedback.confirm.heading", locale, customTranslations) }),
979
979
  duplicates.length > 0 && /* @__PURE__ */ jsxs("div", { className: "feedback-confirm__duplicate-warning", role: "alert", children: [
980
- /* @__PURE__ */ jsx("strong", { children: "Similar feedback already exists:" }),
980
+ /* @__PURE__ */ jsx("strong", { children: t("feedback.confirm.duplicateWarning", locale, customTranslations) }),
981
981
  /* @__PURE__ */ jsx("ul", { children: duplicates.map((d) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: `#${d.id}`, className: "feedback-confirm__duplicate-link", children: [
982
982
  d.title,
983
983
  " (",
@@ -987,7 +987,7 @@ function FeedbackConfirm({ className }) {
987
987
  ] }),
988
988
  /* @__PURE__ */ jsxs("div", { className: "feedback-confirm__field", children: [
989
989
  /* @__PURE__ */ jsxs("label", { htmlFor: "feedback-confirm-title-input", className: "feedback-confirm__label", children: [
990
- t("feedback.confirm.titleLabel", locale, customTranslations) || "Title",
990
+ t("feedback.confirm.titleLabel", locale, customTranslations),
991
991
  " ",
992
992
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "feedback-confirm__required", children: "*" }),
993
993
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: "(required)" })
@@ -1016,7 +1016,7 @@ function FeedbackConfirm({ className }) {
1016
1016
  )
1017
1017
  ] }),
1018
1018
  /* @__PURE__ */ jsxs("div", { className: "feedback-confirm__field", children: [
1019
- /* @__PURE__ */ jsx("label", { htmlFor: "feedback-confirm-description", className: "feedback-confirm__label", children: t("feedback.confirm.descriptionLabel", locale, customTranslations) || "Description" }),
1019
+ /* @__PURE__ */ jsx("label", { htmlFor: "feedback-confirm-description", className: "feedback-confirm__label", children: t("feedback.confirm.descriptionLabel", locale, customTranslations) }),
1020
1020
  /* @__PURE__ */ jsx(
1021
1021
  "textarea",
1022
1022
  {
@@ -1030,7 +1030,7 @@ function FeedbackConfirm({ className }) {
1030
1030
  ] }),
1031
1031
  /* @__PURE__ */ jsxs("div", { className: "feedback-confirm__field", children: [
1032
1032
  /* @__PURE__ */ jsxs("label", { htmlFor: "feedback-confirm-type", className: "feedback-confirm__label", children: [
1033
- t("feedback.confirm.typeLabel", locale, customTranslations) || "Type",
1033
+ t("feedback.confirm.typeLabel", locale, customTranslations),
1034
1034
  " ",
1035
1035
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "feedback-confirm__required", children: "*" }),
1036
1036
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: "(required)" })
@@ -1065,7 +1065,7 @@ function FeedbackConfirm({ className }) {
1065
1065
  )
1066
1066
  ] }),
1067
1067
  /* @__PURE__ */ jsxs("div", { className: "feedback-confirm__field", children: [
1068
- /* @__PURE__ */ jsx("label", { htmlFor: "feedback-confirm-scope", className: "feedback-confirm__label", children: t("feedback.confirm.scopeLabel", locale, customTranslations) || "Scope" }),
1068
+ /* @__PURE__ */ jsx("label", { htmlFor: "feedback-confirm-scope", className: "feedback-confirm__label", children: t("feedback.confirm.scopeLabel", locale, customTranslations) }),
1069
1069
  /* @__PURE__ */ jsxs(
1070
1070
  "select",
1071
1071
  {
@@ -1074,16 +1074,16 @@ function FeedbackConfirm({ className }) {
1074
1074
  value: values.scope,
1075
1075
  onChange: (e) => handleChange("scope", e.target.value),
1076
1076
  children: [
1077
- /* @__PURE__ */ jsx("option", { value: "", children: "Select scope (optional)" }),
1078
- /* @__PURE__ */ jsx("option", { value: "isolated", children: "Isolated" }),
1079
- /* @__PURE__ */ jsx("option", { value: "cross-cutting", children: "Cross-cutting" }),
1080
- /* @__PURE__ */ jsx("option", { value: "systemic", children: "Systemic" })
1077
+ /* @__PURE__ */ jsx("option", { value: "", children: t("feedback.confirm.scopePlaceholder", locale, customTranslations) }),
1078
+ /* @__PURE__ */ jsx("option", { value: "isolated", children: t("feedback.confirm.scope.isolated", locale, customTranslations) }),
1079
+ /* @__PURE__ */ jsx("option", { value: "cross-cutting", children: t("feedback.confirm.scope.crossCutting", locale, customTranslations) }),
1080
+ /* @__PURE__ */ jsx("option", { value: "systemic", children: t("feedback.confirm.scope.systemic", locale, customTranslations) })
1081
1081
  ]
1082
1082
  }
1083
1083
  )
1084
1084
  ] }),
1085
1085
  /* @__PURE__ */ jsxs("div", { className: "feedback-confirm__field", children: [
1086
- /* @__PURE__ */ jsx("label", { htmlFor: "feedback-confirm-size", className: "feedback-confirm__label", children: t("feedback.confirm.sizeLabel", locale, customTranslations) || "Size" }),
1086
+ /* @__PURE__ */ jsx("label", { htmlFor: "feedback-confirm-size", className: "feedback-confirm__label", children: t("feedback.confirm.sizeLabel", locale, customTranslations) }),
1087
1087
  /* @__PURE__ */ jsxs(
1088
1088
  "select",
1089
1089
  {
@@ -1092,11 +1092,11 @@ function FeedbackConfirm({ className }) {
1092
1092
  value: values.size,
1093
1093
  onChange: (e) => handleChange("size", e.target.value),
1094
1094
  children: [
1095
- /* @__PURE__ */ jsx("option", { value: "", children: "Select size (optional)" }),
1096
- /* @__PURE__ */ jsx("option", { value: "trivial", children: "Trivial" }),
1097
- /* @__PURE__ */ jsx("option", { value: "small", children: "Small" }),
1098
- /* @__PURE__ */ jsx("option", { value: "medium", children: "Medium" }),
1099
- /* @__PURE__ */ jsx("option", { value: "large", children: "Large" })
1095
+ /* @__PURE__ */ jsx("option", { value: "", children: t("feedback.confirm.sizePlaceholder", locale, customTranslations) }),
1096
+ /* @__PURE__ */ jsx("option", { value: "trivial", children: t("feedback.confirm.size.trivial", locale, customTranslations) }),
1097
+ /* @__PURE__ */ jsx("option", { value: "small", children: t("feedback.confirm.size.small", locale, customTranslations) }),
1098
+ /* @__PURE__ */ jsx("option", { value: "medium", children: t("feedback.confirm.size.medium", locale, customTranslations) }),
1099
+ /* @__PURE__ */ jsx("option", { value: "large", children: t("feedback.confirm.size.large", locale, customTranslations) })
1100
1100
  ]
1101
1101
  }
1102
1102
  )
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sources":["../src/react/FeedbackContext.ts","../src/react/FeedbackProvider.tsx","../src/react/useFeedback.ts","../src/react/FeedbackButton.tsx","../src/react/FeedbackDialogue.tsx","../src/react/FeedbackRecorder.tsx","../src/react/FeedbackConfirm.tsx","../src/react/FeedbackPanel.tsx"],"sourcesContent":["/**\n * FeedbackContext — typed React context for the feedback intake UI.\n *\n * Import from: import { FeedbackProvider, useFeedback } from 'anton-bakker-feedback/react'\n *\n * This file defines the shape of context value and creates the context object.\n * The provider and hook are in separate files to keep each unit focused.\n */\nimport { createContext } from 'react';\nimport type { FeedbackType, FeedbackScope, FeedbackSize, DialogueEntry } from '../core/types.js';\nimport type { ResolvedFeedbackConfig } from '../core/config.js';\n\n// ─────────────────────────────────────────────────────────────────\n// State Machine Types\n// ─────────────────────────────────────────────────────────────────\n\n/** Screenshots captured during a session */\nexport type ScreenshotEntry = {\n id: string;\n dataUrl: string; // base64 or blob URL\n capturedAt: string;\n};\n\n/** Context captured at the point of opening the feedback panel */\nexport interface CapturedContext {\n url: string;\n pageTitle: string;\n userAgent: string;\n viewport: { width: number; height: number };\n locale: string;\n consoleErrors: string[];\n screenshots: ScreenshotEntry[];\n duplicateCandidates: Array<{ id: string; title: string }>;\n customFields?: Record<string, string | number | boolean>;\n}\n\n/** Classification suggested by the dialogue */\nexport interface DialogueClassification {\n type?: FeedbackType;\n scope?: FeedbackScope;\n size?: FeedbackSize;\n title?: string;\n description?: string;\n confidence?: number;\n}\n\n/** State when idle — no panel open */\nexport interface IdleState {\n status: 'idle';\n}\n\n/** State when dialogue is in progress */\nexport interface DialogueState {\n status: 'dialogue';\n history: DialogueEntry[];\n step: number;\n totalSteps: number;\n awaitingResponse: boolean;\n classification: DialogueClassification;\n error: string | null;\n}\n\n/** State during screen capture (recording sub-states) */\nexport interface RecordingState {\n status: 'recording';\n recordingMode: 'minimised' | 'capture' | 'review';\n captureCount: number;\n}\n\n/** State when confirming submission */\nexport interface ConfirmingState {\n status: 'confirming';\n classification: DialogueClassification;\n submitting: boolean;\n error: string | null;\n}\n\n/** State after successful submission */\nexport interface SubmittedState {\n status: 'submitted';\n feedbackId: string;\n}\n\n/** Discriminated union of all possible panel states */\nexport type FeedbackPanelState =\n | IdleState\n | DialogueState\n | RecordingState\n | ConfirmingState\n | SubmittedState;\n\n// ─────────────────────────────────────────────────────────────────\n// Capability Detection\n// ─────────────────────────────────────────────────────────────────\n\n/** Runtime capability detection results */\nexport interface Capabilities {\n /** Whether Bedrock dialogue is available */\n bedrock: boolean;\n /** Whether DynamoDB storage is available */\n dynamodb: boolean;\n /** Capability detection has completed */\n detected: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Context Value Shape\n// ─────────────────────────────────────────────────────────────────\n\n/** The value provided by FeedbackContext */\nexport interface FeedbackContextValue {\n /** Resolved config with all defaults applied */\n config: ResolvedFeedbackConfig;\n /** Current panel state */\n state: FeedbackPanelState;\n /** Convenience: is panel currently open (not idle) */\n isOpen: boolean;\n /** Context captured when panel was opened */\n capturedContext: CapturedContext | null;\n /** Runtime capabilities (Bedrock, DynamoDB) */\n capabilities: Capabilities;\n /** Active i18n locale */\n locale: string;\n /** Open the panel, optionally merging context */\n open: (trigger?: Partial<CapturedContext>) => void;\n /** Close and reset to idle */\n close: () => void;\n /** Internal: dispatch state transitions */\n dispatch: (action: FeedbackAction) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Actions\n// ─────────────────────────────────────────────────────────────────\n\nexport type FeedbackAction =\n | { type: 'OPEN'; payload: Partial<CapturedContext> }\n | { type: 'OPEN' }\n | { type: 'CLOSE' }\n | { type: 'SEND_MESSAGE'; payload: { message: string } }\n | { type: 'RECEIVE_RESPONSE'; payload: { response: string; step: number; totalSteps: number; classification?: DialogueClassification; done?: boolean } }\n | { type: 'DIALOGUE_ERROR'; payload: { error: string } }\n | { type: 'START_RECORDING' }\n | { type: 'SET_RECORDING_MODE'; payload: { mode: 'minimised' | 'capture' | 'review' } }\n | { type: 'ADD_SCREENSHOT'; payload: ScreenshotEntry }\n | { type: 'REMOVE_SCREENSHOT'; payload: { id: string } }\n | { type: 'CONFIRM'; payload: { classification?: DialogueClassification } }\n | { type: 'CONFIRM' }\n | { type: 'SUBMIT_START' }\n | { type: 'SUBMIT_SUCCESS'; payload: { feedbackId: string } }\n | { type: 'SUBMIT_ERROR'; payload: { error: string } }\n | { type: 'SET_CAPABILITIES'; payload: Capabilities };\n\n// ─────────────────────────────────────────────────────────────────\n// Context Object\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackContext — React context for the intake UI.\n *\n * Throws a descriptive error when consumed outside FeedbackProvider:\n * import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nexport const FeedbackContext = createContext<FeedbackContextValue | null>(null);\n\nFeedbackContext.displayName = 'FeedbackContext';\n","/**\n * FeedbackProvider — context provider for the feedback intake UI.\n *\n * Usage:\n * import { FeedbackProvider } from 'anton-bakker-feedback/react'\n *\n * Example:\n * <FeedbackProvider\n * config={{ appName: 'my-app', transport: { submitUrl: 'https://…' } }}\n * canSubmitFeedback={(user) => user.role !== 'guest'}\n * locale=\"nl\"\n * >\n * <App />\n * </FeedbackProvider>\n *\n * RBAC gate: if canSubmitFeedback returns false, the provider renders\n * children without injecting FeedbackPanel. No button is shown.\n *\n * Progressive enhancement: capability detection (Bedrock, DynamoDB) runs\n * on mount and is stored in context — no consumer code branches required.\n *\n * The \"Skip to form\" action is surfaced via capabilities.bedrock === false\n * so FeedbackDialogue can render the \"Skip to form\" button automatically.\n * Consumers import from: import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nimport React, {\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useReducer,\n Suspense,\n lazy,\n} from 'react';\nimport {\n FeedbackContext,\n type FeedbackAction,\n type FeedbackPanelState,\n type FeedbackContextValue,\n type CapturedContext,\n type Capabilities,\n type DialogueClassification,\n} from './FeedbackContext.js';\nimport type { FeedbackConfig } from '../core/config.js';\nimport { validateConfig, resolveConfig } from '../core/config.js';\nimport { detectLocale } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackProviderProps {\n /** Feedback configuration */\n config: FeedbackConfig;\n /**\n * RBAC gate — if this returns false, FeedbackPanel is not rendered.\n * Pass the current authenticated user object.\n */\n canSubmitFeedback: (user?: unknown) => boolean;\n /** Optional i18n locale override (BCP 47). Defaults to navigator.language → 'en' */\n locale?: string;\n /** The current user (passed to canSubmitFeedback) */\n user?: unknown;\n children: ReactNode;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Initial State\n// ─────────────────────────────────────────────────────────────────\n\nconst INITIAL_PANEL_STATE: FeedbackPanelState = { status: 'idle' };\n\nfunction buildInitialCapturedContext(): CapturedContext {\n const isClient = typeof window !== 'undefined';\n return {\n url: isClient ? window.location.href : '',\n pageTitle: isClient ? document.title : '',\n userAgent: isClient ? navigator.userAgent : '',\n viewport: isClient\n ? { width: window.innerWidth, height: window.innerHeight }\n : { width: 0, height: 0 },\n locale: isClient ? navigator.language : 'en',\n consoleErrors: [],\n screenshots: [],\n duplicateCandidates: [],\n };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Reducer\n// ─────────────────────────────────────────────────────────────────\n\ninterface ProviderInternalState {\n panel: FeedbackPanelState;\n capturedContext: CapturedContext | null;\n capabilities: Capabilities;\n preservedClassification: DialogueClassification;\n}\n\nconst INITIAL_INTERNAL_STATE: ProviderInternalState = {\n panel: INITIAL_PANEL_STATE,\n capturedContext: null,\n capabilities: { bedrock: false, dynamodb: false, detected: false },\n preservedClassification: {},\n};\n\nfunction feedbackReducer(\n state: ProviderInternalState,\n action: FeedbackAction,\n): ProviderInternalState {\n switch (action.type) {\n case 'OPEN': {\n if (state.panel.status !== 'idle') return state; // idempotent\n const base = buildInitialCapturedContext();\n const merged: CapturedContext =\n 'payload' in action && action.payload\n ? { ...base, ...action.payload }\n : base;\n\n const newPanel: FeedbackPanelState = state.capabilities.bedrock\n ? {\n status: 'dialogue',\n history: [],\n step: 0,\n totalSteps: 5,\n awaitingResponse: false,\n classification: {},\n error: null,\n }\n : {\n status: 'confirming',\n classification: {},\n submitting: false,\n error: null,\n };\n\n return { ...state, panel: newPanel, capturedContext: merged };\n }\n\n case 'CLOSE':\n return {\n ...state,\n panel: INITIAL_PANEL_STATE,\n capturedContext: null,\n preservedClassification: {},\n };\n\n case 'SEND_MESSAGE': {\n if (state.panel.status !== 'dialogue') return state;\n const newHistory = [\n ...state.panel.history,\n {\n role: 'user' as const,\n content: action.payload.message,\n timestamp: new Date().toISOString(),\n },\n ];\n return {\n ...state,\n panel: {\n ...state.panel,\n history: newHistory,\n awaitingResponse: true,\n error: null,\n },\n };\n }\n\n case 'RECEIVE_RESPONSE': {\n if (state.panel.status !== 'dialogue') return state;\n const { response, step, totalSteps, classification, done } = action.payload;\n const mergedClassification: DialogueClassification = {\n ...state.panel.classification,\n ...(classification ?? {}),\n };\n const newHistory = [\n ...state.panel.history,\n {\n role: 'assistant' as const,\n content: response,\n timestamp: new Date().toISOString(),\n },\n ];\n\n if (done === true) {\n return {\n ...state,\n panel: {\n status: 'confirming',\n classification: mergedClassification,\n submitting: false,\n error: null,\n },\n preservedClassification: mergedClassification,\n };\n }\n\n return {\n ...state,\n panel: {\n ...state.panel,\n history: newHistory,\n step,\n totalSteps,\n awaitingResponse: false,\n classification: mergedClassification,\n },\n };\n }\n\n case 'DIALOGUE_ERROR': {\n if (state.panel.status !== 'dialogue') return state;\n return {\n ...state,\n panel: {\n ...state.panel,\n awaitingResponse: false,\n error: action.payload.error,\n },\n };\n }\n\n case 'START_RECORDING': {\n const currentClassification =\n state.panel.status === 'dialogue' || state.panel.status === 'confirming'\n ? state.panel.classification\n : state.preservedClassification;\n\n return {\n ...state,\n panel: {\n status: 'recording',\n recordingMode: 'capture',\n captureCount: state.capturedContext?.screenshots.length ?? 0,\n },\n preservedClassification: currentClassification,\n };\n }\n\n case 'SET_RECORDING_MODE': {\n if (state.panel.status !== 'recording') return state;\n return {\n ...state,\n panel: {\n ...state.panel,\n recordingMode: action.payload.mode,\n },\n };\n }\n\n case 'ADD_SCREENSHOT': {\n if (!state.capturedContext) return state;\n const existing = state.capturedContext.screenshots;\n if (existing.length >= 10) return state;\n const updated = [...existing, action.payload];\n return {\n ...state,\n capturedContext: { ...state.capturedContext, screenshots: updated },\n panel:\n state.panel.status === 'recording'\n ? { ...state.panel, captureCount: updated.length }\n : state.panel,\n };\n }\n\n case 'REMOVE_SCREENSHOT': {\n if (!state.capturedContext) return state;\n const filtered = state.capturedContext.screenshots.filter(\n (s) => s.id !== action.payload.id,\n );\n return {\n ...state,\n capturedContext: { ...state.capturedContext, screenshots: filtered },\n panel:\n state.panel.status === 'recording'\n ? { ...state.panel, captureCount: filtered.length }\n : state.panel,\n };\n }\n\n case 'CONFIRM': {\n const classification =\n ('payload' in action && action.payload?.classification)\n ? action.payload.classification\n : state.preservedClassification;\n return {\n ...state,\n panel: {\n status: 'confirming',\n classification,\n submitting: false,\n error: null,\n },\n preservedClassification: classification,\n };\n }\n\n case 'SUBMIT_START': {\n if (state.panel.status !== 'confirming') return state;\n return {\n ...state,\n panel: { ...state.panel, submitting: true, error: null },\n };\n }\n\n case 'SUBMIT_SUCCESS': {\n return {\n ...state,\n panel: {\n status: 'submitted',\n feedbackId: action.payload.feedbackId,\n },\n };\n }\n\n case 'SUBMIT_ERROR': {\n if (state.panel.status !== 'confirming') return state;\n return {\n ...state,\n panel: {\n ...state.panel,\n submitting: false,\n error: action.payload.error,\n },\n };\n }\n\n case 'SET_CAPABILITIES': {\n return { ...state, capabilities: action.payload };\n }\n\n default:\n return state;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Capability Detection\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Detects whether Bedrock (dialogue) and DynamoDB (storage) are available.\n * Pings configured transport URLs; falls back to minimal on any error.\n */\nasync function detectCapabilities(\n submitUrl: string,\n dialogueUrl: string | undefined,\n): Promise<Capabilities> {\n let dynamodb = true;\n let bedrock = !!dialogueUrl;\n\n if (dialogueUrl) {\n try {\n const ctrl = new AbortController();\n const timeoutId = setTimeout(() => ctrl.abort(), 3000);\n await fetch(dialogueUrl, { method: 'HEAD', signal: ctrl.signal });\n clearTimeout(timeoutId);\n bedrock = true;\n } catch {\n bedrock = false;\n }\n }\n\n try {\n const ctrl = new AbortController();\n const timeoutId = setTimeout(() => ctrl.abort(), 3000);\n await fetch(submitUrl, { method: 'HEAD', signal: ctrl.signal });\n clearTimeout(timeoutId);\n dynamodb = true;\n } catch {\n dynamodb = false;\n }\n\n return { bedrock, dynamodb, detected: true };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Lazy FeedbackPanel (avoids circular import at definition time)\n// ─────────────────────────────────────────────────────────────────\n\nconst FeedbackPanelLazy = lazy(\n () => import('./FeedbackPanel.js').then((m) => ({ default: m.FeedbackPanel })),\n);\n\n// ─────────────────────────────────────────────────────────────────\n// Provider Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackProvider wraps your application root and provides the feedback\n * context, RBAC gate, capability detection, and i18n locale.\n *\n * \"Skip to form\" — when capabilities.bedrock is false, FeedbackDialogue\n * will show the \"Skip to form\" button to allow direct access to FeedbackConfirm.\n *\n * Import: import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nexport function FeedbackProvider({\n config,\n canSubmitFeedback,\n locale: localeProp,\n user,\n children,\n}: FeedbackProviderProps): React.JSX.Element {\n // ── Config Validation ──────────────────────────────────────────\n const resolvedConfig = useMemo(() => {\n const validation = validateConfig(config);\n if (!validation.valid) {\n console.error(\n '[anton-bakker-feedback/react] FeedbackProvider received invalid config:\\n' +\n validation.errors\n .map((e) => ` • ${e}`)\n .join('\\n') +\n '\\nFeedbackPanel will not render until config is corrected.',\n );\n return null;\n }\n return resolveConfig(config);\n }, [config]);\n\n // ── Locale ────────────────────────────────────────────────────\n const locale = useMemo(() => {\n const preferred =\n localeProp ??\n (typeof navigator !== 'undefined' ? navigator.language : 'en');\n return detectLocale(preferred);\n }, [localeProp]);\n\n // ── RBAC Gate ─────────────────────────────────────────────────\n const authorised = useMemo(\n () => canSubmitFeedback(user),\n [canSubmitFeedback, user],\n );\n\n // ── State ─────────────────────────────────────────────────────\n const [internalState, dispatch] = useReducer(\n feedbackReducer,\n INITIAL_INTERNAL_STATE,\n );\n\n // ── Capability Detection on Mount ──────────────────────────────\n useEffect(() => {\n if (!resolvedConfig) return;\n let cancelled = false;\n\n void detectCapabilities(\n resolvedConfig.transport.submitUrl,\n resolvedConfig.transport.dialogueUrl,\n ).then((caps) => {\n if (!cancelled) {\n dispatch({ type: 'SET_CAPABILITIES', payload: caps });\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [resolvedConfig]);\n\n // ── Public API ────────────────────────────────────────────────\n const open = useCallback(\n (trigger?: Partial<CapturedContext>) => {\n if (trigger !== undefined) {\n dispatch({ type: 'OPEN', payload: trigger });\n } else {\n dispatch({ type: 'OPEN' });\n }\n },\n [],\n );\n\n const close = useCallback(() => {\n dispatch({ type: 'CLOSE' });\n }, []);\n\n // ── Context Value ─────────────────────────────────────────────\n const contextValue = useMemo((): FeedbackContextValue | null => {\n if (!resolvedConfig) return null;\n return {\n config: resolvedConfig,\n state: internalState.panel,\n isOpen: internalState.panel.status !== 'idle',\n capturedContext: internalState.capturedContext,\n capabilities: internalState.capabilities,\n locale,\n open,\n close,\n dispatch,\n };\n }, [resolvedConfig, internalState, locale, open, close]);\n\n // ── Render ────────────────────────────────────────────────────\n if (!contextValue) {\n // Invalid config — render children without panel (graceful degradation)\n return React.createElement(React.Fragment, null, children);\n }\n\n return (\n <FeedbackContext.Provider value={contextValue}>\n {children}\n {authorised && (\n <Suspense fallback={null}>\n <FeedbackPanelLazy />\n </Suspense>\n )}\n </FeedbackContext.Provider>\n );\n}\n","/**\n * useFeedback — imperative hook surface for the feedback intake UI.\n *\n * Returns { open, close, state, isOpen, capturedContext }.\n *\n * Throws a descriptive error when called outside FeedbackProvider:\n * import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nimport { useContext } from 'react';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport type {\n FeedbackPanelState,\n CapturedContext,\n FeedbackContextValue,\n} from './FeedbackContext.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Return Type\n// ─────────────────────────────────────────────────────────────────\n\nexport interface UseFeedbackReturn {\n /** Open the panel. Merges optional trigger context. Idempotent if already open. */\n open: (trigger?: Partial<CapturedContext>) => void;\n /** Close the panel and reset state to idle. */\n close: () => void;\n /** Current panel state (discriminated union) */\n state: FeedbackPanelState;\n /** Convenience: true when panel is not idle */\n isOpen: boolean;\n /** Context captured when panel was opened (null when idle) */\n capturedContext: CapturedContext | null;\n /** Full context value (for advanced use cases) */\n _context: FeedbackContextValue;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Hook\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * useFeedback() — consumes the FeedbackContext.\n *\n * Must be called inside a component that is a descendant of FeedbackProvider.\n * Throws a descriptive error otherwise.\n *\n * @example\n * const { open, close, state, isOpen } = useFeedback();\n */\nexport function useFeedback(): UseFeedbackReturn {\n const ctx = useContext(FeedbackContext);\n\n if (ctx === null) {\n throw new Error(\n '[useFeedback] Could not find FeedbackContext. ' +\n 'Make sure your component is rendered inside a <FeedbackProvider>. ' +\n 'Import from: import { FeedbackProvider } from \"anton-bakker-feedback/react\"',\n );\n }\n\n return {\n open: ctx.open,\n close: ctx.close,\n state: ctx.state,\n isOpen: ctx.isOpen,\n capturedContext: ctx.capturedContext,\n _context: ctx,\n };\n}\n","/**\n * FeedbackButton — floating action button for triggering the feedback panel.\n *\n * Layout 1: Trigger\n * Rendered by FeedbackPanel when state === 'idle'.\n * Not mounted when RBAC gate blocks rendering.\n *\n * Theming via CSS custom properties:\n * --feedback-button-bg\n * --feedback-button-color\n * --feedback-button-hover-bg\n * --feedback-button-focus-ring\n */\nimport React from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { useContext } from 'react';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackButtonProps {\n /** Additional CSS class names (merged via className prop) */\n className?: string;\n /** Override the default tooltip/aria-label text */\n label?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Styles (CSS custom properties for theming)\n// ─────────────────────────────────────────────────────────────────\n\nconst BUTTON_STYLES = `\n.feedback-button {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: var(--feedback-z-index, 9998);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 12px 16px;\n border-radius: 24px;\n border: none;\n cursor: pointer;\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n background-color: var(--feedback-button-bg, #4f46e5);\n color: var(--feedback-button-color, #ffffff);\n box-shadow: var(--feedback-button-shadow, 0 4px 12px rgba(79, 70, 229, 0.35));\n transition: background-color 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;\n -webkit-user-select: none;\n user-select: none;\n outline: none;\n}\n\n.feedback-button:hover {\n background-color: var(--feedback-button-hover-bg, #4338ca);\n box-shadow: var(--feedback-button-hover-shadow, 0 6px 16px rgba(79, 70, 229, 0.45));\n}\n\n.feedback-button:focus-visible {\n outline: 2px solid var(--feedback-button-focus-ring, #4f46e5);\n outline-offset: 2px;\n box-shadow: var(--feedback-button-shadow, 0 4px 12px rgba(79, 70, 229, 0.35)),\n 0 0 0 4px var(--feedback-button-focus-ring-spread, rgba(79, 70, 229, 0.2));\n}\n\n.feedback-button:active {\n background-color: var(--feedback-button-active-bg, #3730a3);\n transform: scale(0.97);\n}\n\n.feedback-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n}\n\n.feedback-button__icon {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n}\n`;\n\n// ─────────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────────\n\nlet stylesInjected = false;\n\nfunction injectStyles() {\n if (stylesInjected || typeof document === 'undefined') return;\n const style = document.createElement('style');\n style.setAttribute('data-feedback-button', '');\n style.textContent = BUTTON_STYLES;\n document.head.appendChild(style);\n stylesInjected = true;\n}\n\n/**\n * FeedbackButton — floating action button.\n *\n * Only renders when state === 'idle' (FeedbackPanel controls this).\n * Calls open() from context on click — does NOT manage modal state.\n *\n * Keyboard accessible: Tab focuses, Enter/Space activates.\n * Uses i18n key 'feedback.button.tooltip' for aria-label.\n */\nexport function FeedbackButton({ className, label }: FeedbackButtonProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, open } = useFeedback();\n\n // Inject styles on first render\n React.useEffect(() => {\n injectStyles();\n }, []);\n\n // Only render when idle — FeedbackPanel also gates this, but be defensive\n if (state.status !== 'idle') return null;\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n const tooltipFromI18n =\n t('feedback.button.tooltip', locale, customTranslations) ||\n t('feedback.button.open', locale, customTranslations);\n const tooltip = label ?? tooltipFromI18n;\n\n const handleClick = () => {\n open();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n open();\n }\n };\n\n const classNames = ['feedback-button', className].filter(Boolean).join(' ');\n\n return (\n <button\n type=\"button\"\n className={classNames}\n aria-label={tooltip}\n title={tooltip}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n <svg\n className=\"feedback-button__icon\"\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n {tooltip}\n </button>\n );\n}\n","/**\n * FeedbackDialogue — Layout 2: Conversation\n *\n * Chat interface with:\n * - Message history (user/assistant bubbles)\n * - Controlled textarea (Enter sends, Shift+Enter newline)\n * - Typing indicator while awaiting LLM response\n * - Step counter (step N of totalSteps)\n * - \"Start recording\" action (available after first exchange)\n * - \"Skip to form\" action when capabilities.bedrock === false or on error\n * - Auto-scroll to newest message\n * - Auto-transition to confirming when dialogue signals done\n */\nimport React, {\n useCallback,\n useEffect,\n useRef,\n useState,\n useContext,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\nimport type { DialogueEntry } from '../core/types.js';\nimport type { FeedbackType, FeedbackScope, FeedbackSize } from '../core/types.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackDialogueProps {\n className?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Sub-components\n// ─────────────────────────────────────────────────────────────────\n\nfunction MessageBubble({ entry }: { entry: DialogueEntry }) {\n const isUser = entry.role === 'user';\n return (\n <div\n className={`feedback-dialogue__bubble feedback-dialogue__bubble--${entry.role}`}\n data-role={entry.role}\n >\n <span className=\"feedback-dialogue__bubble-label\">\n {isUser ? 'You' : 'Assistant'}\n </span>\n <p className=\"feedback-dialogue__bubble-content\">{entry.content}</p>\n </div>\n );\n}\n\nfunction TypingIndicator() {\n return (\n <div className=\"feedback-dialogue__typing\" aria-live=\"polite\" aria-label=\"Assistant is typing\">\n <span className=\"feedback-dialogue__typing-dot\" />\n <span className=\"feedback-dialogue__typing-dot\" />\n <span className=\"feedback-dialogue__typing-dot\" />\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Main Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackDialogue — chat interface for the dialogue phase.\n *\n * Calls dispatch to send messages via the dialogue transport.\n * \"Skip to form\" is available when capabilities.bedrock === false or on error —\n * transitions directly to confirming with empty classification.\n */\nexport function FeedbackDialogue({ className }: FeedbackDialogueProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, _context } = useFeedback();\n const [inputValue, setInputValue] = useState('');\n const messagesEndRef = useRef<HTMLDivElement>(null);\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n const capabilities = ctx?.capabilities;\n const dialogueUrl = ctx?.config.transport.dialogueUrl;\n\n const isDialogue = state.status === 'dialogue';\n const history = isDialogue ? state.history : [];\n const step = isDialogue ? state.step : 0;\n const totalSteps = isDialogue ? state.totalSteps : 5;\n const awaitingResponse = isDialogue && state.awaitingResponse;\n const error = isDialogue ? state.error : null;\n\n const canSkipToForm =\n capabilities?.bedrock === false || (error !== null && error !== '');\n\n // Auto-scroll to newest message\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n }, [history.length, awaitingResponse]);\n\n // Send message handler\n const handleSend = useCallback(() => {\n const trimmed = inputValue.trim();\n if (!trimmed || awaitingResponse || !isDialogue) return;\n\n _context.dispatch({ type: 'SEND_MESSAGE', payload: { message: trimmed } });\n setInputValue('');\n\n if (dialogueUrl) {\n const url = dialogueUrl;\n const newStep = step + 1;\n const done = newStep >= totalSteps;\n\n void fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ message: trimmed, step: newStep }),\n })\n .then(async (res) => {\n if (!res.ok) throw new Error(`Dialogue error: ${res.status}`);\n const data = (await res.json()) as {\n response: string;\n classification?: {\n type?: FeedbackType;\n scope?: FeedbackScope;\n size?: FeedbackSize;\n };\n done?: boolean;\n };\n _context.dispatch({\n type: 'RECEIVE_RESPONSE',\n payload: {\n response: data.response,\n step: newStep,\n totalSteps,\n ...(data.classification !== undefined && { classification: data.classification }),\n done: data.done ?? done,\n },\n });\n })\n .catch((err: unknown) => {\n _context.dispatch({\n type: 'DIALOGUE_ERROR',\n payload: {\n error:\n err instanceof Error ? err.message : 'Dialogue request failed',\n },\n });\n });\n } else {\n // No dialogue URL — simulate simple response\n setTimeout(() => {\n const newStepVal = step + 1;\n const doneVal = newStepVal >= totalSteps;\n _context.dispatch({\n type: 'RECEIVE_RESPONSE',\n payload: {\n response: doneVal\n ? t('feedback.dialogue.complete', locale, customTranslations)\n : t('feedback.dialogue.turnPrompt', locale, customTranslations),\n step: newStepVal,\n totalSteps,\n done: doneVal,\n },\n });\n }, 800);\n }\n }, [inputValue, awaitingResponse, isDialogue, _context, step, totalSteps, locale, customTranslations, dialogueUrl]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n // Only render in dialogue state (after all hooks)\n if (state.status !== 'dialogue') return null;\n\n const handleSkipToForm = () => {\n _context.dispatch({ type: 'CONFIRM', payload: { classification: {} } });\n };\n\n const handleStartRecording = () => {\n _context.dispatch({ type: 'START_RECORDING' });\n };\n\n const hasExchange = history.length > 0;\n\n const classNames = ['feedback-dialogue', className].filter(Boolean).join(' ');\n\n return (\n <div className={classNames} role=\"region\" aria-label=\"Feedback dialogue\">\n {/* Step counter */}\n <div className=\"feedback-dialogue__progress\" aria-label={`Step ${step} of ${totalSteps}`}>\n <span className=\"feedback-dialogue__step\">\n {step > 0 ? `${step} / ${totalSteps}` : ''}\n </span>\n </div>\n\n {/* Message history */}\n <div\n className=\"feedback-dialogue__messages\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n >\n {history.length === 0 && (\n <p className=\"feedback-dialogue__opening\">\n {t('feedback.dialogue.opening', locale, customTranslations)}\n </p>\n )}\n {history.map((entry, idx) => (\n <MessageBubble key={`${entry.role}-${idx}`} entry={entry} />\n ))}\n {awaitingResponse && <TypingIndicator />}\n <div ref={messagesEndRef} />\n </div>\n\n {/* Error banner */}\n {error && (\n <div className=\"feedback-dialogue__error\" role=\"alert\">\n {error}\n </div>\n )}\n\n {/* Action toolbar */}\n <div className=\"feedback-dialogue__actions\">\n {hasExchange && (\n <button\n type=\"button\"\n className=\"feedback-dialogue__action-btn\"\n onClick={handleStartRecording}\n >\n {t('feedback.button.screenshot', locale, customTranslations)}\n </button>\n )}\n {canSkipToForm && (\n <button\n type=\"button\"\n className=\"feedback-dialogue__skip-btn\"\n onClick={handleSkipToForm}\n aria-label=\"Skip to form\"\n >\n Skip to form\n </button>\n )}\n </div>\n\n {/* Input area */}\n <div className=\"feedback-dialogue__input-area\">\n <textarea\n className=\"feedback-dialogue__textarea\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n disabled={awaitingResponse}\n placeholder={\n awaitingResponse\n ? t('feedback.dialogue.thinking', locale, customTranslations)\n : 'Type your message… (Enter to send, Shift+Enter for newline)'\n }\n aria-label=\"Dialogue input\"\n rows={3}\n />\n <button\n type=\"button\"\n className=\"feedback-dialogue__send-btn\"\n onClick={handleSend}\n disabled={awaitingResponse || !inputValue.trim()}\n aria-label=\"Send message\"\n >\n {awaitingResponse ? (\n <span className=\"feedback-dialogue__spinner\" aria-hidden=\"true\" />\n ) : (\n '→'\n )}\n </button>\n </div>\n </div>\n );\n}\n","/**\n * FeedbackRecorder — Layout 3: Screen Capture\n *\n * Three sub-states:\n * 1. minimised — badge on FAB, underlying app is interactive\n * 2. capture — crosshair cursor overlay, click to capture\n * 3. review — thumbnail grid, delete per screenshot, \"Done\" → confirming\n *\n * Screenshots stored in capturedContext.screenshots[] (up to 10 entries).\n * All sub-state transitions driven through dispatch (no local state for flow).\n */\nimport React, {\n useCallback,\n useEffect,\n useState,\n useContext,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport type { ScreenshotEntry } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackRecorderProps {\n className?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Screenshot Capture Utility\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Attempts to capture the current viewport as a base64 PNG.\n * Uses dynamic import of html2canvas; falls back to getDisplayMedia.\n */\nasync function captureScreenshot(): Promise<string | null> {\n // Try html2canvas (dynamic import via computed string — optional dependency)\n try {\n const h2cName = 'html2canvas';\n const html2canvasModule = (await import(/* @vite-ignore */ h2cName).catch(\n () => null,\n )) as { default?: (el: HTMLElement) => Promise<HTMLCanvasElement> } | null;\n if (html2canvasModule?.default) {\n const canvas = await html2canvasModule.default(document.body);\n return canvas.toDataURL('image/png');\n }\n } catch {\n // html2canvas not available — fall through\n }\n\n // Try getDisplayMedia as secondary approach\n try {\n if (\n typeof navigator !== 'undefined' &&\n navigator.mediaDevices &&\n 'getDisplayMedia' in navigator.mediaDevices\n ) {\n const stream = await (\n navigator.mediaDevices as MediaDevices & {\n getDisplayMedia: (constraints?: MediaStreamConstraints) => Promise<MediaStream>;\n }\n ).getDisplayMedia({ video: true });\n const video = document.createElement('video');\n video.srcObject = stream;\n await video.play();\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n canvas.getContext('2d')?.drawImage(video, 0, 0);\n stream.getTracks().forEach((track) => track.stop());\n return canvas.toDataURL('image/png');\n }\n } catch {\n // getDisplayMedia not available or denied\n }\n\n return null;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackRecorder — screen capture component.\n *\n * Minimised mode: badge floats over the UI; app remains interactive.\n * Capture mode: crosshair cursor overlay, click anywhere captures.\n * Review mode: thumbnail grid with delete buttons; \"Done\" goes to confirming.\n */\nexport function FeedbackRecorder({ className }: FeedbackRecorderProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, capturedContext, _context } = useFeedback();\n const [apiSupported, setApiSupported] = useState<boolean | null>(null);\n const [capturing, setCapturing] = useState(false);\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n\n const isRecording = state.status === 'recording';\n const recordingMode = isRecording ? state.recordingMode : null;\n const captureCount = isRecording ? state.captureCount : 0;\n const screenshots = capturedContext?.screenshots ?? [];\n\n // Check API availability on mount (unconditional)\n useEffect(() => {\n if (!isRecording) return;\n const hasGetDisplayMedia =\n typeof navigator !== 'undefined' &&\n !!navigator.mediaDevices &&\n 'getDisplayMedia' in navigator.mediaDevices;\n // Assume supported until proven otherwise (html2canvas is unknown at load time)\n setApiSupported(hasGetDisplayMedia);\n }, [isRecording]);\n\n // Handle capture click\n const handleCaptureClick = useCallback(async () => {\n if (capturing || screenshots.length >= 10) return;\n setCapturing(true);\n\n try {\n const dataUrl = await captureScreenshot();\n if (dataUrl) {\n const entry: ScreenshotEntry = {\n id: crypto.randomUUID(),\n dataUrl,\n capturedAt: new Date().toISOString(),\n };\n _context.dispatch({ type: 'ADD_SCREENSHOT', payload: entry });\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'review' },\n });\n } else {\n setApiSupported(false);\n }\n } catch {\n setApiSupported(false);\n } finally {\n setCapturing(false);\n }\n }, [capturing, screenshots.length, _context]);\n\n // Only render in recording state (after all hooks)\n if (state.status !== 'recording') return null;\n\n // ── Minimised mode ─────────────────────────────────────────────\n if (recordingMode === 'minimised') {\n return (\n <button\n type=\"button\"\n className={['feedback-recorder__badge', className].filter(Boolean).join(' ')}\n onClick={() =>\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'capture' },\n })\n }\n aria-label={`${captureCount} screenshot${captureCount !== 1 ? 's' : ''} captured. Click to resume`}\n >\n 📷 {captureCount}\n </button>\n );\n }\n\n // ── API not supported ──────────────────────────────────────────\n if (apiSupported === false) {\n return (\n <div className={['feedback-recorder', className].filter(Boolean).join(' ')}>\n <p className=\"feedback-recorder__unsupported-msg\">\n {t('feedback.toast.screenshotError', locale, customTranslations)}\n {' '}Screenshot capture is not supported in this browser.\n </p>\n <button\n type=\"button\"\n className=\"feedback-recorder__continue-btn\"\n onClick={() => _context.dispatch({ type: 'CONFIRM', payload: {} })}\n >\n Continue without screenshots\n </button>\n </div>\n );\n }\n\n // ── Capture mode ───────────────────────────────────────────────\n if (recordingMode === 'capture') {\n return (\n <div\n className={['feedback-recorder', 'feedback-recorder--capture', className]\n .filter(Boolean)\n .join(' ')}\n style={{ cursor: 'crosshair' }}\n onClick={() => void handleCaptureClick()}\n role=\"button\"\n tabIndex={0}\n aria-label=\"Click anywhere to capture screenshot\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n void handleCaptureClick();\n }\n }}\n >\n <div className=\"feedback-recorder__capture-overlay\">\n <p>\n {capturing ? 'Capturing…' : 'Click anywhere to capture a screenshot'}\n </p>\n <button\n type=\"button\"\n className=\"feedback-recorder__minimise-btn\"\n onClick={(e) => {\n e.stopPropagation();\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'minimised' },\n });\n }}\n >\n Minimise\n </button>\n {screenshots.length > 0 && (\n <button\n type=\"button\"\n className=\"feedback-recorder__review-btn\"\n onClick={(e) => {\n e.stopPropagation();\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'review' },\n });\n }}\n >\n Review ({screenshots.length})\n </button>\n )}\n </div>\n </div>\n );\n }\n\n // ── Review mode ────────────────────────────────────────────────\n return (\n <div\n className={['feedback-recorder', 'feedback-recorder--review', className]\n .filter(Boolean)\n .join(' ')}\n >\n <h3 className=\"feedback-recorder__title\">\n {screenshots.length === 0\n ? 'No screenshots yet'\n : `${screenshots.length} screenshot${screenshots.length !== 1 ? 's' : ''}`}\n </h3>\n\n {screenshots.length > 0 && (\n <div className=\"feedback-recorder__thumbnails\" role=\"list\">\n {screenshots.map((shot) => (\n <div\n key={shot.id}\n className=\"feedback-recorder__thumbnail\"\n role=\"listitem\"\n >\n <img\n src={shot.dataUrl}\n alt={`Screenshot captured at ${shot.capturedAt}`}\n className=\"feedback-recorder__thumbnail-img\"\n />\n <button\n type=\"button\"\n className=\"feedback-recorder__delete-btn\"\n aria-label={`${t('feedback.button.removeScreenshot', locale, customTranslations)} captured at ${shot.capturedAt}`}\n onClick={() =>\n _context.dispatch({\n type: 'REMOVE_SCREENSHOT',\n payload: { id: shot.id },\n })\n }\n >\n ×\n </button>\n </div>\n ))}\n </div>\n )}\n\n <div className=\"feedback-recorder__review-actions\">\n {screenshots.length < 10 && (\n <button\n type=\"button\"\n className=\"feedback-recorder__add-btn\"\n onClick={() =>\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'capture' },\n })\n }\n >\n {t('feedback.button.screenshot', locale, customTranslations)}\n </button>\n )}\n <button\n type=\"button\"\n className=\"feedback-recorder__done-btn\"\n onClick={() => _context.dispatch({ type: 'CONFIRM', payload: {} })}\n >\n Done\n </button>\n </div>\n </div>\n );\n}\n","/**\n * FeedbackConfirm — Layout 4: Review Form\n *\n * Pre-filled from dialogueState.classification and capturedContext.\n * Editable: title, description, type, scope, size.\n * Read-only: screenshot thumbnails, console log count, system info summary.\n * Duplicate detection: banner with links if capturedContext.duplicateCandidates is non-empty.\n * Submit: calls configured endpoint; shows spinner; success → submitted; error → inline retry.\n */\nimport React, {\n useCallback,\n useEffect,\n useState,\n useContext,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\nimport type { FeedbackType, FeedbackScope, FeedbackSize } from '../core/types.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackConfirmProps {\n className?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Form State\n// ─────────────────────────────────────────────────────────────────\n\ninterface FormValues {\n title: string;\n description: string;\n type: FeedbackType | '';\n scope: FeedbackScope | '';\n size: FeedbackSize | '';\n}\n\ninterface FormErrors {\n title?: string;\n type?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Submit Payload\n// ─────────────────────────────────────────────────────────────────\n\ninterface SubmitPayload {\n title: string;\n description?: string | undefined;\n type: FeedbackType;\n scope?: FeedbackScope | undefined;\n size?: FeedbackSize | undefined;\n screenshots?: Array<{ dataUrl: string; capturedAt: string }> | undefined;\n consoleErrors?: string[] | undefined;\n systemInfo?: {\n url?: string | undefined;\n userAgent?: string | undefined;\n viewport?: { width: number; height: number } | undefined;\n locale?: string | undefined;\n } | undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Validation\n// ─────────────────────────────────────────────────────────────────\n\nfunction validateForm(values: FormValues): FormErrors {\n const errors: FormErrors = {};\n if (!values.title || values.title.trim().length < 10) {\n errors.title = 'Title is required (minimum 10 characters)';\n }\n if (!values.type) {\n errors.type = 'Type is required';\n }\n return errors;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackConfirm — pre-filled review form.\n *\n * All editable fields use i18n keys feedback.confirm.* for labels.\n * Duplicate warning shown when capturedContext.duplicateCandidates is non-empty.\n * Submit posts to config.transport.submitUrl; maps 201 → submitted state.\n * On error: inline error with retry; modal stays open.\n */\nexport function FeedbackConfirm({ className }: FeedbackConfirmProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, capturedContext, _context } = useFeedback();\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n const submitUrl = ctx?.config.transport.submitUrl ?? '';\n\n // Hooks must be called unconditionally before any early return\n const confirming = state.status === 'confirming';\n const classification = confirming ? state.classification : {};\n const submitting = confirming && state.submitting;\n const submitError = confirming ? state.error : null;\n\n const [values, setValues] = useState<FormValues>(() => ({\n title: classification.title ?? '',\n description: classification.description ?? '',\n type: classification.type ?? '',\n scope: classification.scope ?? '',\n size: classification.size ?? '',\n }));\n\n const [touched, setTouched] = useState<Partial<Record<keyof FormValues, boolean>>>({});\n\n // Update form values when classification changes\n useEffect(() => {\n if (!confirming) return;\n setValues((prev) => ({\n title: classification.title ?? prev.title,\n description: classification.description ?? prev.description,\n type: classification.type ?? prev.type,\n scope: classification.scope ?? prev.scope,\n size: classification.size ?? prev.size,\n }));\n }, [confirming, classification.title, classification.description, classification.type, classification.scope, classification.size]);\n\n const formErrors = validateForm(values);\n const isValid = Object.keys(formErrors).length === 0;\n\n const screenshots = capturedContext?.screenshots ?? [];\n const consoleErrors = capturedContext?.consoleErrors ?? [];\n const duplicates = capturedContext?.duplicateCandidates ?? [];\n\n const handleChange = useCallback(\n (field: keyof FormValues, value: string) => {\n setValues((prev) => ({ ...prev, [field]: value }));\n setTouched((prev) => ({ ...prev, [field]: true }));\n },\n [],\n );\n\n const handleSubmit = useCallback(async () => {\n setTouched({ title: true, type: true });\n if (!isValid || !values.type) return;\n\n _context.dispatch({ type: 'SUBMIT_START' });\n\n const submissionPayload: SubmitPayload = {\n title: values.title.trim(),\n type: values.type as FeedbackType,\n };\n const descTrimmed = values.description.trim();\n if (descTrimmed) submissionPayload.description = descTrimmed;\n if (values.scope) submissionPayload.scope = values.scope as FeedbackScope;\n if (values.size) submissionPayload.size = values.size as FeedbackSize;\n if (screenshots.length > 0) {\n submissionPayload.screenshots = screenshots.map((s) => ({\n dataUrl: s.dataUrl,\n capturedAt: s.capturedAt,\n }));\n }\n if (consoleErrors.length > 0) submissionPayload.consoleErrors = consoleErrors;\n if (capturedContext) {\n submissionPayload.systemInfo = {\n url: capturedContext.url,\n userAgent: capturedContext.userAgent,\n viewport: capturedContext.viewport,\n locale: capturedContext.locale,\n };\n }\n\n try {\n const response = await fetch(submitUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(submissionPayload),\n });\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n _context.dispatch({\n type: 'SUBMIT_ERROR',\n payload: {\n error:\n body.error ??\n t('feedback.status.error', locale, customTranslations),\n },\n });\n return;\n }\n\n const result = (await response.json()) as { id?: string; feedbackId?: string };\n const feedbackId = result.id ?? result.feedbackId ?? 'unknown';\n _context.dispatch({ type: 'SUBMIT_SUCCESS', payload: { feedbackId } });\n } catch (err) {\n _context.dispatch({\n type: 'SUBMIT_ERROR',\n payload: {\n error:\n err instanceof Error\n ? err.message\n : t('feedback.status.error', locale, customTranslations),\n },\n });\n }\n }, [isValid, values, screenshots, consoleErrors, capturedContext, locale, customTranslations, submitUrl, _context]);\n\n // Only render in confirming state (after all hooks)\n if (state.status !== 'confirming') return null;\n\n const classNames = ['feedback-confirm', className].filter(Boolean).join(' ');\n\n return (\n <form\n className={classNames}\n onSubmit={(e) => {\n e.preventDefault();\n void handleSubmit();\n }}\n aria-label=\"Submit feedback\"\n noValidate\n >\n <h2 id=\"feedback-confirm-title\" className=\"feedback-confirm__title\">\n Review your feedback\n </h2>\n\n {/* Duplicate warning */}\n {duplicates.length > 0 && (\n <div className=\"feedback-confirm__duplicate-warning\" role=\"alert\">\n <strong>Similar feedback already exists:</strong>\n <ul>\n {duplicates.map((d) => (\n <li key={d.id}>\n <a href={`#${d.id}`} className=\"feedback-confirm__duplicate-link\">\n {d.title} ({d.id})\n </a>\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {/* Title */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-title-input\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.titleLabel', locale, customTranslations) || 'Title'}\n {' '}\n <span aria-hidden=\"true\" className=\"feedback-confirm__required\">*</span>\n <span className=\"sr-only\">(required)</span>\n </label>\n <input\n id=\"feedback-confirm-title-input\"\n type=\"text\"\n className=\"feedback-confirm__input\"\n value={values.title}\n onChange={(e) => handleChange('title', e.target.value)}\n aria-required=\"true\"\n aria-invalid={touched.title && !!formErrors.title}\n aria-describedby={\n touched.title && formErrors.title\n ? 'feedback-confirm-title-error'\n : undefined\n }\n />\n {touched.title && formErrors.title && (\n <span\n id=\"feedback-confirm-title-error\"\n className=\"feedback-confirm__error\"\n role=\"alert\"\n >\n {formErrors.title}\n </span>\n )}\n </div>\n\n {/* Description */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-description\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.descriptionLabel', locale, customTranslations) || 'Description'}\n </label>\n <textarea\n id=\"feedback-confirm-description\"\n className=\"feedback-confirm__textarea\"\n value={values.description}\n onChange={(e) => handleChange('description', e.target.value)}\n rows={4}\n />\n </div>\n\n {/* Type */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-type\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.typeLabel', locale, customTranslations) || 'Type'}\n {' '}\n <span aria-hidden=\"true\" className=\"feedback-confirm__required\">*</span>\n <span className=\"sr-only\">(required)</span>\n </label>\n <select\n id=\"feedback-confirm-type\"\n className=\"feedback-confirm__select\"\n value={values.type}\n onChange={(e) => handleChange('type', e.target.value)}\n aria-required=\"true\"\n aria-invalid={touched.type && !!formErrors.type}\n aria-describedby={\n touched.type && formErrors.type\n ? 'feedback-confirm-type-error'\n : undefined\n }\n >\n <option value=\"\">{t('feedback.form.typePlaceholder', locale, customTranslations)}</option>\n <option value=\"bug\">{t('feedback.types.bug', locale, customTranslations)}</option>\n <option value=\"feature\">{t('feedback.types.feature', locale, customTranslations)}</option>\n <option value=\"ux\">{t('feedback.types.ux', locale, customTranslations)}</option>\n <option value=\"other\">{t('feedback.types.other', locale, customTranslations)}</option>\n </select>\n {touched.type && formErrors.type && (\n <span\n id=\"feedback-confirm-type-error\"\n className=\"feedback-confirm__error\"\n role=\"alert\"\n >\n {formErrors.type}\n </span>\n )}\n </div>\n\n {/* Scope */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-scope\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.scopeLabel', locale, customTranslations) || 'Scope'}\n </label>\n <select\n id=\"feedback-confirm-scope\"\n className=\"feedback-confirm__select\"\n value={values.scope}\n onChange={(e) => handleChange('scope', e.target.value)}\n >\n <option value=\"\">Select scope (optional)</option>\n <option value=\"isolated\">Isolated</option>\n <option value=\"cross-cutting\">Cross-cutting</option>\n <option value=\"systemic\">Systemic</option>\n </select>\n </div>\n\n {/* Size */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-size\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.sizeLabel', locale, customTranslations) || 'Size'}\n </label>\n <select\n id=\"feedback-confirm-size\"\n className=\"feedback-confirm__select\"\n value={values.size}\n onChange={(e) => handleChange('size', e.target.value)}\n >\n <option value=\"\">Select size (optional)</option>\n <option value=\"trivial\">Trivial</option>\n <option value=\"small\">Small</option>\n <option value=\"medium\">Medium</option>\n <option value=\"large\">Large</option>\n </select>\n </div>\n\n {/* Read-only context section */}\n <div className=\"feedback-confirm__context-section\" aria-label=\"Captured context (read-only)\">\n {screenshots.length > 0 && (\n <div className=\"feedback-confirm__screenshots\">\n <p className=\"feedback-confirm__context-label\">\n Screenshots ({screenshots.length})\n </p>\n <div className=\"feedback-confirm__screenshot-thumbnails\">\n {screenshots.map((s) => (\n <img\n key={s.id}\n src={s.dataUrl}\n alt={`Screenshot from ${s.capturedAt}`}\n className=\"feedback-confirm__screenshot-thumb\"\n />\n ))}\n </div>\n </div>\n )}\n\n {consoleErrors.length > 0 && (\n <p className=\"feedback-confirm__console-count\">\n {consoleErrors.length} console error{consoleErrors.length !== 1 ? 's' : ''} captured\n </p>\n )}\n\n {capturedContext && (\n <p className=\"feedback-confirm__system-info\">\n {capturedContext.url} — {capturedContext.userAgent.slice(0, 60)}…\n </p>\n )}\n </div>\n\n {/* Submit error */}\n {submitError && (\n <div className=\"feedback-confirm__submit-error\" role=\"alert\">\n {submitError}\n <button\n type=\"button\"\n className=\"feedback-confirm__retry-btn\"\n onClick={() => void handleSubmit()}\n >\n {t('feedback.status.retry', locale, customTranslations)}\n </button>\n </div>\n )}\n\n {/* Submit button */}\n <button\n type=\"submit\"\n className=\"feedback-confirm__submit-btn\"\n disabled={submitting}\n aria-label={\n submitting\n ? t('feedback.status.submitting', locale, customTranslations)\n : t('feedback.button.submit', locale, customTranslations)\n }\n >\n {submitting ? (\n <>\n <span className=\"feedback-confirm__spinner\" aria-hidden=\"true\" />\n {t('feedback.status.submitting', locale, customTranslations)}\n </>\n ) : (\n t('feedback.button.submit', locale, customTranslations)\n )}\n </button>\n </form>\n );\n}\n","/**\n * FeedbackPanel — Layout 7: Modal Container\n *\n * Renders into a React portal (document.body) to escape stacking context.\n * Shows FeedbackButton when idle; switches to the active layout component.\n *\n * Accessibility:\n * - role=\"dialog\" with aria-labelledby and aria-modal=\"true\"\n * - Focus trap: Tab/Shift+Tab cycles within open modal\n * - Focus returns to trigger element on close\n * - Escape triggers discard confirmation if dialogue has started\n * - CSS custom properties for theming\n */\nimport React, {\n useCallback,\n useEffect,\n useRef,\n useState,\n type RefObject,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { useContext } from 'react';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { FeedbackButton } from './FeedbackButton.js';\nimport { FeedbackDialogue } from './FeedbackDialogue.js';\nimport { FeedbackRecorder } from './FeedbackRecorder.js';\nimport { FeedbackConfirm } from './FeedbackConfirm.js';\nimport { t } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Portal helper (avoids react-dom peer dependency at type level)\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Portal — renders children into document.body.\n * Uses dynamic import to avoid hard typing of react-dom.\n */\nfunction Portal({ children }: { children: React.ReactNode }): React.JSX.Element | null {\n const [isClient, setIsClient] = useState(false);\n\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n if (!isClient || typeof document === 'undefined') {\n return React.createElement(React.Fragment, null, children);\n }\n\n // Use a simple div approach for rendering \"into body\"\n // This is equivalent to createPortal(children, document.body) in behaviour\n // without needing the @types/react-dom peer\n return React.createElement(\n React.Fragment,\n null,\n children,\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Focus Trap\n// ─────────────────────────────────────────────────────────────────\n\nconst FOCUSABLE_SELECTORS = [\n 'a[href]',\n 'button:not([disabled])',\n 'textarea:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ');\n\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS),\n ).filter((el) => el.offsetParent !== null);\n}\n\nfunction useFocusTrap(containerRef: RefObject<HTMLElement | null>, isActive: boolean) {\n useEffect(() => {\n if (!isActive || !containerRef.current) return;\n\n const container = containerRef.current;\n const focusables = getFocusableElements(container);\n if (focusables.length > 0) {\n focusables[0]?.focus();\n } else {\n container.focus();\n }\n\n function handleKeyDown(e: KeyboardEvent) {\n if (e.key !== 'Tab') return;\n const elements = getFocusableElements(container);\n if (elements.length === 0) {\n e.preventDefault();\n return;\n }\n const first = elements[0];\n const last = elements[elements.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last?.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first?.focus();\n }\n }\n }\n\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n };\n }, [isActive, containerRef]);\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Submitted view\n// ─────────────────────────────────────────────────────────────────\n\nfunction SubmittedView({\n feedbackId,\n onClose,\n locale,\n customTranslations,\n}: {\n feedbackId: string;\n onClose: () => void;\n locale: string;\n customTranslations?: Record<string, Record<string, string>> | undefined;\n}) {\n return (\n <div className=\"feedback-panel__submitted\" role=\"status\">\n <p className=\"feedback-panel__submitted-icon\" aria-hidden=\"true\">✓</p>\n <h3 className=\"feedback-panel__submitted-title\">\n {t('feedback.status.success', locale, customTranslations)}\n </h3>\n <p className=\"feedback-panel__submitted-id\">\n Your feedback ID: <strong>{feedbackId}</strong>\n </p>\n <button\n type=\"button\"\n className=\"feedback-panel__close-btn\"\n onClick={onClose}\n >\n {t('feedback.button.close', locale, customTranslations)}\n </button>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Panel Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackPanel — modal container component.\n *\n * Renders a React portal to document.body.\n * Controls state-based layout switching and accessibility.\n */\nexport function FeedbackPanel(): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, isOpen, close, _context } = useFeedback();\n const dialogRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLElement | null>(null);\n const [showDiscardConfirm, setShowDiscardConfirm] = useState(false);\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n\n // Track trigger element for focus restoration\n useEffect(() => {\n if (isOpen) {\n triggerRef.current = document.activeElement as HTMLElement;\n } else {\n setShowDiscardConfirm(false);\n }\n }, [isOpen]);\n\n // Restore focus on close\n useEffect(() => {\n if (!isOpen && triggerRef.current) {\n triggerRef.current.focus();\n triggerRef.current = null;\n }\n }, [isOpen]);\n\n // Focus trap\n useFocusTrap(dialogRef, isOpen);\n\n // Escape key handler\n const handleEscape = useCallback(() => {\n const hasStarted =\n state.status === 'dialogue' &&\n (state.history.length > 0 || state.step > 0);\n if (hasStarted) {\n setShowDiscardConfirm(true);\n } else {\n close();\n }\n }, [state, close]);\n\n useEffect(() => {\n if (!isOpen) return;\n\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault();\n handleEscape();\n }\n }\n\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [isOpen, handleEscape]);\n\n // Backdrop click handler\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n handleEscape();\n }\n },\n [handleEscape],\n );\n\n const handleDiscardConfirm = () => {\n setShowDiscardConfirm(false);\n close();\n };\n\n const handleDiscardCancel = () => {\n setShowDiscardConfirm(false);\n };\n\n // Determine the modal title based on state\n const modalTitleId = 'feedback-panel-title';\n const modalTitle =\n state.status === 'idle'\n ? ''\n : state.status === 'dialogue'\n ? 'Feedback conversation'\n : state.status === 'recording'\n ? 'Capture screenshot'\n : state.status === 'confirming'\n ? 'Review your feedback'\n : 'Feedback submitted';\n\n return (\n <Portal>\n {/* Floating button — always rendered when idle */}\n {state.status === 'idle' && <FeedbackButton />}\n\n {/* Modal — rendered when panel is open */}\n {isOpen && (\n <div\n className=\"feedback-panel__backdrop\"\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 9999,\n backgroundColor: 'var(--feedback-backdrop-bg, rgba(0, 0, 0, 0.5))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n onClick={handleBackdropClick}\n aria-hidden=\"false\"\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={modalTitleId}\n tabIndex={-1}\n className=\"feedback-panel__dialog\"\n style={{\n position: 'relative',\n background: 'var(--feedback-panel-bg, #ffffff)',\n borderRadius: 'var(--feedback-panel-radius, 12px)',\n boxShadow: 'var(--feedback-panel-shadow, 0 20px 60px rgba(0,0,0,0.3))',\n width: 'var(--feedback-panel-width, 540px)',\n maxWidth: '95vw',\n maxHeight: '90vh',\n overflow: 'auto',\n padding: 'var(--feedback-panel-padding, 24px)',\n }}\n >\n {/* Hidden title for screen readers */}\n <h2 id={modalTitleId} className=\"feedback-panel__title\" aria-live=\"polite\">\n {modalTitle}\n </h2>\n\n {/* Close button */}\n <button\n type=\"button\"\n className=\"feedback-panel__close-button\"\n aria-label={t('feedback.button.close', locale, customTranslations)}\n onClick={handleEscape}\n style={{\n position: 'absolute',\n top: 16,\n right: 16,\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontSize: 20,\n lineHeight: 1,\n padding: '4px 8px',\n color: 'inherit',\n }}\n >\n ×\n </button>\n\n {/* Discard confirmation overlay */}\n {showDiscardConfirm && (\n <div\n className=\"feedback-panel__discard-confirm\"\n role=\"alertdialog\"\n aria-labelledby=\"feedback-panel-discard-title\"\n style={{\n position: 'absolute',\n inset: 0,\n background: 'var(--feedback-panel-bg, #ffffff)',\n borderRadius: 'inherit',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 16,\n padding: 24,\n zIndex: 1,\n }}\n >\n <p id=\"feedback-panel-discard-title\" style={{ fontWeight: 600 }}>\n Discard your feedback?\n </p>\n <p>Your progress will be lost.</p>\n <div style={{ display: 'flex', gap: 12 }}>\n <button\n type=\"button\"\n className=\"feedback-panel__discard-confirm-btn\"\n onClick={handleDiscardConfirm}\n >\n Discard\n </button>\n <button\n type=\"button\"\n className=\"feedback-panel__discard-cancel-btn\"\n onClick={handleDiscardCancel}\n autoFocus\n >\n Continue editing\n </button>\n </div>\n </div>\n )}\n\n {/* Layout switching */}\n {state.status === 'dialogue' && <FeedbackDialogue />}\n {state.status === 'recording' && <FeedbackRecorder />}\n {state.status === 'confirming' && <FeedbackConfirm />}\n {state.status === 'submitted' && (\n <SubmittedView\n feedbackId={state.feedbackId}\n onClose={() => _context.dispatch({ type: 'CLOSE' })}\n locale={locale}\n customTranslations={customTranslations ?? undefined}\n />\n )}\n </div>\n </div>\n )}\n </Portal>\n );\n}\n"],"names":["React"],"mappings":"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"}
1
+ {"version":3,"file":"react.js","sources":["../src/react/FeedbackContext.ts","../src/react/FeedbackProvider.tsx","../src/react/useFeedback.ts","../src/react/FeedbackButton.tsx","../src/react/FeedbackDialogue.tsx","../src/react/FeedbackRecorder.tsx","../src/react/FeedbackConfirm.tsx","../src/react/FeedbackPanel.tsx"],"sourcesContent":["/**\n * FeedbackContext — typed React context for the feedback intake UI.\n *\n * Import from: import { FeedbackProvider, useFeedback } from 'anton-bakker-feedback/react'\n *\n * This file defines the shape of context value and creates the context object.\n * The provider and hook are in separate files to keep each unit focused.\n */\nimport { createContext } from 'react';\nimport type { FeedbackType, FeedbackScope, FeedbackSize, DialogueEntry } from '../core/types.js';\nimport type { ResolvedFeedbackConfig } from '../core/config.js';\n\n// ─────────────────────────────────────────────────────────────────\n// State Machine Types\n// ─────────────────────────────────────────────────────────────────\n\n/** Screenshots captured during a session */\nexport type ScreenshotEntry = {\n id: string;\n dataUrl: string; // base64 or blob URL\n capturedAt: string;\n};\n\n/** Context captured at the point of opening the feedback panel */\nexport interface CapturedContext {\n url: string;\n pageTitle: string;\n userAgent: string;\n viewport: { width: number; height: number };\n locale: string;\n consoleErrors: string[];\n screenshots: ScreenshotEntry[];\n duplicateCandidates: Array<{ id: string; title: string }>;\n customFields?: Record<string, string | number | boolean>;\n}\n\n/** Classification suggested by the dialogue */\nexport interface DialogueClassification {\n type?: FeedbackType;\n scope?: FeedbackScope;\n size?: FeedbackSize;\n title?: string;\n description?: string;\n confidence?: number;\n}\n\n/** State when idle — no panel open */\nexport interface IdleState {\n status: 'idle';\n}\n\n/** State when dialogue is in progress */\nexport interface DialogueState {\n status: 'dialogue';\n history: DialogueEntry[];\n step: number;\n totalSteps: number;\n awaitingResponse: boolean;\n classification: DialogueClassification;\n error: string | null;\n}\n\n/** State during screen capture (recording sub-states) */\nexport interface RecordingState {\n status: 'recording';\n recordingMode: 'minimised' | 'capture' | 'review';\n captureCount: number;\n}\n\n/** State when confirming submission */\nexport interface ConfirmingState {\n status: 'confirming';\n classification: DialogueClassification;\n submitting: boolean;\n error: string | null;\n}\n\n/** State after successful submission */\nexport interface SubmittedState {\n status: 'submitted';\n feedbackId: string;\n}\n\n/** Discriminated union of all possible panel states */\nexport type FeedbackPanelState =\n | IdleState\n | DialogueState\n | RecordingState\n | ConfirmingState\n | SubmittedState;\n\n// ─────────────────────────────────────────────────────────────────\n// Capability Detection\n// ─────────────────────────────────────────────────────────────────\n\n/** Runtime capability detection results */\nexport interface Capabilities {\n /** Whether Bedrock dialogue is available */\n bedrock: boolean;\n /** Whether DynamoDB storage is available */\n dynamodb: boolean;\n /** Capability detection has completed */\n detected: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Context Value Shape\n// ─────────────────────────────────────────────────────────────────\n\n/** The value provided by FeedbackContext */\nexport interface FeedbackContextValue {\n /** Resolved config with all defaults applied */\n config: ResolvedFeedbackConfig;\n /** Current panel state */\n state: FeedbackPanelState;\n /** Convenience: is panel currently open (not idle) */\n isOpen: boolean;\n /** Context captured when panel was opened */\n capturedContext: CapturedContext | null;\n /** Runtime capabilities (Bedrock, DynamoDB) */\n capabilities: Capabilities;\n /** Active i18n locale */\n locale: string;\n /** Open the panel, optionally merging context */\n open: (trigger?: Partial<CapturedContext>) => void;\n /** Close and reset to idle */\n close: () => void;\n /** Internal: dispatch state transitions */\n dispatch: (action: FeedbackAction) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Actions\n// ─────────────────────────────────────────────────────────────────\n\nexport type FeedbackAction =\n | { type: 'OPEN'; payload: Partial<CapturedContext> }\n | { type: 'OPEN' }\n | { type: 'CLOSE' }\n | { type: 'SEND_MESSAGE'; payload: { message: string } }\n | { type: 'RECEIVE_RESPONSE'; payload: { response: string; step: number; totalSteps: number; classification?: DialogueClassification; done?: boolean } }\n | { type: 'DIALOGUE_ERROR'; payload: { error: string } }\n | { type: 'START_RECORDING' }\n | { type: 'SET_RECORDING_MODE'; payload: { mode: 'minimised' | 'capture' | 'review' } }\n | { type: 'ADD_SCREENSHOT'; payload: ScreenshotEntry }\n | { type: 'REMOVE_SCREENSHOT'; payload: { id: string } }\n | { type: 'CONFIRM'; payload: { classification?: DialogueClassification } }\n | { type: 'CONFIRM' }\n | { type: 'SUBMIT_START' }\n | { type: 'SUBMIT_SUCCESS'; payload: { feedbackId: string } }\n | { type: 'SUBMIT_ERROR'; payload: { error: string } }\n | { type: 'SET_CAPABILITIES'; payload: Capabilities };\n\n// ─────────────────────────────────────────────────────────────────\n// Context Object\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackContext — React context for the intake UI.\n *\n * Throws a descriptive error when consumed outside FeedbackProvider:\n * import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nexport const FeedbackContext = createContext<FeedbackContextValue | null>(null);\n\nFeedbackContext.displayName = 'FeedbackContext';\n","/**\n * FeedbackProvider — context provider for the feedback intake UI.\n *\n * Usage:\n * import { FeedbackProvider } from 'anton-bakker-feedback/react'\n *\n * Example:\n * <FeedbackProvider\n * config={{ appName: 'my-app', transport: { submitUrl: 'https://…' } }}\n * canSubmitFeedback={(user) => user.role !== 'guest'}\n * locale=\"nl\"\n * >\n * <App />\n * </FeedbackProvider>\n *\n * RBAC gate: if canSubmitFeedback returns false, the provider renders\n * children without injecting FeedbackPanel. No button is shown.\n *\n * Progressive enhancement: capability detection (Bedrock, DynamoDB) runs\n * on mount and is stored in context — no consumer code branches required.\n *\n * The \"Skip to form\" action is surfaced via capabilities.bedrock === false\n * so FeedbackDialogue can render the \"Skip to form\" button automatically.\n * Consumers import from: import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nimport React, {\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useReducer,\n Suspense,\n lazy,\n} from 'react';\nimport {\n FeedbackContext,\n type FeedbackAction,\n type FeedbackPanelState,\n type FeedbackContextValue,\n type CapturedContext,\n type Capabilities,\n type DialogueClassification,\n} from './FeedbackContext.js';\nimport type { FeedbackConfig } from '../core/config.js';\nimport { validateConfig, resolveConfig } from '../core/config.js';\nimport { detectLocale } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackProviderProps {\n /** Feedback configuration */\n config: FeedbackConfig;\n /**\n * RBAC gate — if this returns false, FeedbackPanel is not rendered.\n * Pass the current authenticated user object.\n */\n canSubmitFeedback: (user?: unknown) => boolean;\n /** Optional i18n locale override (BCP 47). Defaults to navigator.language → 'en' */\n locale?: string;\n /** The current user (passed to canSubmitFeedback) */\n user?: unknown;\n children: ReactNode;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Initial State\n// ─────────────────────────────────────────────────────────────────\n\nconst INITIAL_PANEL_STATE: FeedbackPanelState = { status: 'idle' };\n\nfunction buildInitialCapturedContext(): CapturedContext {\n const isClient = typeof window !== 'undefined';\n return {\n url: isClient ? window.location.href : '',\n pageTitle: isClient ? document.title : '',\n userAgent: isClient ? navigator.userAgent : '',\n viewport: isClient\n ? { width: window.innerWidth, height: window.innerHeight }\n : { width: 0, height: 0 },\n locale: isClient ? navigator.language : 'en',\n consoleErrors: [],\n screenshots: [],\n duplicateCandidates: [],\n };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Reducer\n// ─────────────────────────────────────────────────────────────────\n\ninterface ProviderInternalState {\n panel: FeedbackPanelState;\n capturedContext: CapturedContext | null;\n capabilities: Capabilities;\n preservedClassification: DialogueClassification;\n}\n\nconst INITIAL_INTERNAL_STATE: ProviderInternalState = {\n panel: INITIAL_PANEL_STATE,\n capturedContext: null,\n capabilities: { bedrock: false, dynamodb: false, detected: false },\n preservedClassification: {},\n};\n\nfunction feedbackReducer(\n state: ProviderInternalState,\n action: FeedbackAction,\n): ProviderInternalState {\n switch (action.type) {\n case 'OPEN': {\n if (state.panel.status !== 'idle') return state; // idempotent\n const base = buildInitialCapturedContext();\n const merged: CapturedContext =\n 'payload' in action && action.payload\n ? { ...base, ...action.payload }\n : base;\n\n const newPanel: FeedbackPanelState = state.capabilities.bedrock\n ? {\n status: 'dialogue',\n history: [],\n step: 0,\n totalSteps: 5,\n awaitingResponse: false,\n classification: {},\n error: null,\n }\n : {\n status: 'confirming',\n classification: {},\n submitting: false,\n error: null,\n };\n\n return { ...state, panel: newPanel, capturedContext: merged };\n }\n\n case 'CLOSE':\n return {\n ...state,\n panel: INITIAL_PANEL_STATE,\n capturedContext: null,\n preservedClassification: {},\n };\n\n case 'SEND_MESSAGE': {\n if (state.panel.status !== 'dialogue') return state;\n const newHistory = [\n ...state.panel.history,\n {\n role: 'user' as const,\n content: action.payload.message,\n timestamp: new Date().toISOString(),\n },\n ];\n return {\n ...state,\n panel: {\n ...state.panel,\n history: newHistory,\n awaitingResponse: true,\n error: null,\n },\n };\n }\n\n case 'RECEIVE_RESPONSE': {\n if (state.panel.status !== 'dialogue') return state;\n const { response, step, totalSteps, classification, done } = action.payload;\n const mergedClassification: DialogueClassification = {\n ...state.panel.classification,\n ...(classification ?? {}),\n };\n const newHistory = [\n ...state.panel.history,\n {\n role: 'assistant' as const,\n content: response,\n timestamp: new Date().toISOString(),\n },\n ];\n\n if (done === true) {\n return {\n ...state,\n panel: {\n status: 'confirming',\n classification: mergedClassification,\n submitting: false,\n error: null,\n },\n preservedClassification: mergedClassification,\n };\n }\n\n return {\n ...state,\n panel: {\n ...state.panel,\n history: newHistory,\n step,\n totalSteps,\n awaitingResponse: false,\n classification: mergedClassification,\n },\n };\n }\n\n case 'DIALOGUE_ERROR': {\n if (state.panel.status !== 'dialogue') return state;\n return {\n ...state,\n panel: {\n ...state.panel,\n awaitingResponse: false,\n error: action.payload.error,\n },\n };\n }\n\n case 'START_RECORDING': {\n const currentClassification =\n state.panel.status === 'dialogue' || state.panel.status === 'confirming'\n ? state.panel.classification\n : state.preservedClassification;\n\n return {\n ...state,\n panel: {\n status: 'recording',\n recordingMode: 'capture',\n captureCount: state.capturedContext?.screenshots.length ?? 0,\n },\n preservedClassification: currentClassification,\n };\n }\n\n case 'SET_RECORDING_MODE': {\n if (state.panel.status !== 'recording') return state;\n return {\n ...state,\n panel: {\n ...state.panel,\n recordingMode: action.payload.mode,\n },\n };\n }\n\n case 'ADD_SCREENSHOT': {\n if (!state.capturedContext) return state;\n const existing = state.capturedContext.screenshots;\n if (existing.length >= 10) return state;\n const updated = [...existing, action.payload];\n return {\n ...state,\n capturedContext: { ...state.capturedContext, screenshots: updated },\n panel:\n state.panel.status === 'recording'\n ? { ...state.panel, captureCount: updated.length }\n : state.panel,\n };\n }\n\n case 'REMOVE_SCREENSHOT': {\n if (!state.capturedContext) return state;\n const filtered = state.capturedContext.screenshots.filter(\n (s) => s.id !== action.payload.id,\n );\n return {\n ...state,\n capturedContext: { ...state.capturedContext, screenshots: filtered },\n panel:\n state.panel.status === 'recording'\n ? { ...state.panel, captureCount: filtered.length }\n : state.panel,\n };\n }\n\n case 'CONFIRM': {\n const classification =\n ('payload' in action && action.payload?.classification)\n ? action.payload.classification\n : state.preservedClassification;\n return {\n ...state,\n panel: {\n status: 'confirming',\n classification,\n submitting: false,\n error: null,\n },\n preservedClassification: classification,\n };\n }\n\n case 'SUBMIT_START': {\n if (state.panel.status !== 'confirming') return state;\n return {\n ...state,\n panel: { ...state.panel, submitting: true, error: null },\n };\n }\n\n case 'SUBMIT_SUCCESS': {\n return {\n ...state,\n panel: {\n status: 'submitted',\n feedbackId: action.payload.feedbackId,\n },\n };\n }\n\n case 'SUBMIT_ERROR': {\n if (state.panel.status !== 'confirming') return state;\n return {\n ...state,\n panel: {\n ...state.panel,\n submitting: false,\n error: action.payload.error,\n },\n };\n }\n\n case 'SET_CAPABILITIES': {\n return { ...state, capabilities: action.payload };\n }\n\n default:\n return state;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Capability Detection\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Detects whether Bedrock (dialogue) and DynamoDB (storage) are available.\n * Pings configured transport URLs; falls back to minimal on any error.\n */\nasync function detectCapabilities(\n submitUrl: string,\n dialogueUrl: string | undefined,\n): Promise<Capabilities> {\n let dynamodb = true;\n let bedrock = !!dialogueUrl;\n\n if (dialogueUrl) {\n try {\n const ctrl = new AbortController();\n const timeoutId = setTimeout(() => ctrl.abort(), 3000);\n await fetch(dialogueUrl, { method: 'HEAD', signal: ctrl.signal });\n clearTimeout(timeoutId);\n bedrock = true;\n } catch {\n bedrock = false;\n }\n }\n\n try {\n const ctrl = new AbortController();\n const timeoutId = setTimeout(() => ctrl.abort(), 3000);\n await fetch(submitUrl, { method: 'HEAD', signal: ctrl.signal });\n clearTimeout(timeoutId);\n dynamodb = true;\n } catch {\n dynamodb = false;\n }\n\n return { bedrock, dynamodb, detected: true };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Lazy FeedbackPanel (avoids circular import at definition time)\n// ─────────────────────────────────────────────────────────────────\n\nconst FeedbackPanelLazy = lazy(\n () => import('./FeedbackPanel.js').then((m) => ({ default: m.FeedbackPanel })),\n);\n\n// ─────────────────────────────────────────────────────────────────\n// Provider Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackProvider wraps your application root and provides the feedback\n * context, RBAC gate, capability detection, and i18n locale.\n *\n * \"Skip to form\" — when capabilities.bedrock is false, FeedbackDialogue\n * will show the \"Skip to form\" button to allow direct access to FeedbackConfirm.\n *\n * Import: import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nexport function FeedbackProvider({\n config,\n canSubmitFeedback,\n locale: localeProp,\n user,\n children,\n}: FeedbackProviderProps): React.JSX.Element {\n // ── Config Validation ──────────────────────────────────────────\n const resolvedConfig = useMemo(() => {\n const validation = validateConfig(config);\n if (!validation.valid) {\n console.error(\n '[anton-bakker-feedback/react] FeedbackProvider received invalid config:\\n' +\n validation.errors\n .map((e) => ` • ${e}`)\n .join('\\n') +\n '\\nFeedbackPanel will not render until config is corrected.',\n );\n return null;\n }\n return resolveConfig(config);\n }, [config]);\n\n // ── Locale ────────────────────────────────────────────────────\n const locale = useMemo(() => {\n const preferred =\n localeProp ??\n (typeof navigator !== 'undefined' ? navigator.language : 'en');\n return detectLocale(preferred);\n }, [localeProp]);\n\n // ── RBAC Gate ─────────────────────────────────────────────────\n const authorised = useMemo(\n () => canSubmitFeedback(user),\n [canSubmitFeedback, user],\n );\n\n // ── State ─────────────────────────────────────────────────────\n const [internalState, dispatch] = useReducer(\n feedbackReducer,\n INITIAL_INTERNAL_STATE,\n );\n\n // ── Capability Detection on Mount ──────────────────────────────\n useEffect(() => {\n if (!resolvedConfig) return;\n let cancelled = false;\n\n void detectCapabilities(\n resolvedConfig.transport.submitUrl,\n resolvedConfig.transport.dialogueUrl,\n ).then((caps) => {\n if (!cancelled) {\n dispatch({ type: 'SET_CAPABILITIES', payload: caps });\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [resolvedConfig]);\n\n // ── Public API ────────────────────────────────────────────────\n const open = useCallback(\n (trigger?: Partial<CapturedContext>) => {\n if (trigger !== undefined) {\n dispatch({ type: 'OPEN', payload: trigger });\n } else {\n dispatch({ type: 'OPEN' });\n }\n },\n [],\n );\n\n const close = useCallback(() => {\n dispatch({ type: 'CLOSE' });\n }, []);\n\n // ── Context Value ─────────────────────────────────────────────\n const contextValue = useMemo((): FeedbackContextValue | null => {\n if (!resolvedConfig) return null;\n return {\n config: resolvedConfig,\n state: internalState.panel,\n isOpen: internalState.panel.status !== 'idle',\n capturedContext: internalState.capturedContext,\n capabilities: internalState.capabilities,\n locale,\n open,\n close,\n dispatch,\n };\n }, [resolvedConfig, internalState, locale, open, close]);\n\n // ── Render ────────────────────────────────────────────────────\n if (!contextValue) {\n // Invalid config — render children without panel (graceful degradation)\n return React.createElement(React.Fragment, null, children);\n }\n\n return (\n <FeedbackContext.Provider value={contextValue}>\n {children}\n {authorised && (\n <Suspense fallback={null}>\n <FeedbackPanelLazy />\n </Suspense>\n )}\n </FeedbackContext.Provider>\n );\n}\n","/**\n * useFeedback — imperative hook surface for the feedback intake UI.\n *\n * Returns { open, close, state, isOpen, capturedContext }.\n *\n * Throws a descriptive error when called outside FeedbackProvider:\n * import { FeedbackProvider } from 'anton-bakker-feedback/react'\n */\nimport { useContext } from 'react';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport type {\n FeedbackPanelState,\n CapturedContext,\n FeedbackContextValue,\n} from './FeedbackContext.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Return Type\n// ─────────────────────────────────────────────────────────────────\n\nexport interface UseFeedbackReturn {\n /** Open the panel. Merges optional trigger context. Idempotent if already open. */\n open: (trigger?: Partial<CapturedContext>) => void;\n /** Close the panel and reset state to idle. */\n close: () => void;\n /** Current panel state (discriminated union) */\n state: FeedbackPanelState;\n /** Convenience: true when panel is not idle */\n isOpen: boolean;\n /** Context captured when panel was opened (null when idle) */\n capturedContext: CapturedContext | null;\n /** Full context value (for advanced use cases) */\n _context: FeedbackContextValue;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Hook\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * useFeedback() — consumes the FeedbackContext.\n *\n * Must be called inside a component that is a descendant of FeedbackProvider.\n * Throws a descriptive error otherwise.\n *\n * @example\n * const { open, close, state, isOpen } = useFeedback();\n */\nexport function useFeedback(): UseFeedbackReturn {\n const ctx = useContext(FeedbackContext);\n\n if (ctx === null) {\n throw new Error(\n '[useFeedback] Could not find FeedbackContext. ' +\n 'Make sure your component is rendered inside a <FeedbackProvider>. ' +\n 'Import from: import { FeedbackProvider } from \"anton-bakker-feedback/react\"',\n );\n }\n\n return {\n open: ctx.open,\n close: ctx.close,\n state: ctx.state,\n isOpen: ctx.isOpen,\n capturedContext: ctx.capturedContext,\n _context: ctx,\n };\n}\n","/**\n * FeedbackButton — floating action button for triggering the feedback panel.\n *\n * Layout 1: Trigger\n * Rendered by FeedbackPanel when state === 'idle'.\n * Not mounted when RBAC gate blocks rendering.\n *\n * Theming via CSS custom properties:\n * --feedback-button-bg\n * --feedback-button-color\n * --feedback-button-hover-bg\n * --feedback-button-focus-ring\n */\nimport React from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { useContext } from 'react';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackButtonProps {\n /** Additional CSS class names (merged via className prop) */\n className?: string;\n /** Override the default tooltip/aria-label text */\n label?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Styles (CSS custom properties for theming)\n// ─────────────────────────────────────────────────────────────────\n\nconst BUTTON_STYLES = `\n.feedback-button {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: var(--feedback-z-index, 9998);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 12px 16px;\n border-radius: 24px;\n border: none;\n cursor: pointer;\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n background-color: var(--feedback-button-bg, #4f46e5);\n color: var(--feedback-button-color, #ffffff);\n box-shadow: var(--feedback-button-shadow, 0 4px 12px rgba(79, 70, 229, 0.35));\n transition: background-color 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;\n -webkit-user-select: none;\n user-select: none;\n outline: none;\n}\n\n.feedback-button:hover {\n background-color: var(--feedback-button-hover-bg, #4338ca);\n box-shadow: var(--feedback-button-hover-shadow, 0 6px 16px rgba(79, 70, 229, 0.45));\n}\n\n.feedback-button:focus-visible {\n outline: 2px solid var(--feedback-button-focus-ring, #4f46e5);\n outline-offset: 2px;\n box-shadow: var(--feedback-button-shadow, 0 4px 12px rgba(79, 70, 229, 0.35)),\n 0 0 0 4px var(--feedback-button-focus-ring-spread, rgba(79, 70, 229, 0.2));\n}\n\n.feedback-button:active {\n background-color: var(--feedback-button-active-bg, #3730a3);\n transform: scale(0.97);\n}\n\n.feedback-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n}\n\n.feedback-button__icon {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n}\n`;\n\n// ─────────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────────\n\nlet stylesInjected = false;\n\nfunction injectStyles() {\n if (stylesInjected || typeof document === 'undefined') return;\n const style = document.createElement('style');\n style.setAttribute('data-feedback-button', '');\n style.textContent = BUTTON_STYLES;\n document.head.appendChild(style);\n stylesInjected = true;\n}\n\n/**\n * FeedbackButton — floating action button.\n *\n * Only renders when state === 'idle' (FeedbackPanel controls this).\n * Calls open() from context on click — does NOT manage modal state.\n *\n * Keyboard accessible: Tab focuses, Enter/Space activates.\n * Uses i18n key 'feedback.button.tooltip' for aria-label.\n */\nexport function FeedbackButton({ className, label }: FeedbackButtonProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, open } = useFeedback();\n\n // Inject styles on first render\n React.useEffect(() => {\n injectStyles();\n }, []);\n\n // Only render when idle — FeedbackPanel also gates this, but be defensive\n if (state.status !== 'idle') return null;\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n const tooltipFromI18n =\n t('feedback.button.tooltip', locale, customTranslations) ||\n t('feedback.button.open', locale, customTranslations);\n const tooltip = label ?? tooltipFromI18n;\n\n const handleClick = () => {\n open();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n open();\n }\n };\n\n const classNames = ['feedback-button', className].filter(Boolean).join(' ');\n\n return (\n <button\n type=\"button\"\n className={classNames}\n aria-label={tooltip}\n title={tooltip}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n <svg\n className=\"feedback-button__icon\"\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n {tooltip}\n </button>\n );\n}\n","/**\n * FeedbackDialogue — Layout 2: Conversation\n *\n * Chat interface with:\n * - Message history (user/assistant bubbles)\n * - Controlled textarea (Enter sends, Shift+Enter newline)\n * - Typing indicator while awaiting LLM response\n * - Step counter (step N of totalSteps)\n * - \"Start recording\" action (available after first exchange)\n * - \"Skip to form\" action when capabilities.bedrock === false or on error\n * - Auto-scroll to newest message\n * - Auto-transition to confirming when dialogue signals done\n */\nimport React, {\n useCallback,\n useEffect,\n useRef,\n useState,\n useContext,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\nimport type { DialogueEntry } from '../core/types.js';\nimport type { FeedbackType, FeedbackScope, FeedbackSize } from '../core/types.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackDialogueProps {\n className?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Sub-components\n// ─────────────────────────────────────────────────────────────────\n\nfunction MessageBubble({ entry }: { entry: DialogueEntry }) {\n const isUser = entry.role === 'user';\n return (\n <div\n className={`feedback-dialogue__bubble feedback-dialogue__bubble--${entry.role}`}\n data-role={entry.role}\n >\n <span className=\"feedback-dialogue__bubble-label\">\n {isUser ? 'You' : 'Assistant'}\n </span>\n <p className=\"feedback-dialogue__bubble-content\">{entry.content}</p>\n </div>\n );\n}\n\nfunction TypingIndicator() {\n return (\n <div className=\"feedback-dialogue__typing\" aria-live=\"polite\" aria-label=\"Assistant is typing\">\n <span className=\"feedback-dialogue__typing-dot\" />\n <span className=\"feedback-dialogue__typing-dot\" />\n <span className=\"feedback-dialogue__typing-dot\" />\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Main Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackDialogue — chat interface for the dialogue phase.\n *\n * Calls dispatch to send messages via the dialogue transport.\n * \"Skip to form\" is available when capabilities.bedrock === false or on error —\n * transitions directly to confirming with empty classification.\n */\nexport function FeedbackDialogue({ className }: FeedbackDialogueProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, _context } = useFeedback();\n const [inputValue, setInputValue] = useState('');\n const messagesEndRef = useRef<HTMLDivElement>(null);\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n const capabilities = ctx?.capabilities;\n const dialogueUrl = ctx?.config.transport.dialogueUrl;\n\n const isDialogue = state.status === 'dialogue';\n const history = isDialogue ? state.history : [];\n const step = isDialogue ? state.step : 0;\n const totalSteps = isDialogue ? state.totalSteps : 5;\n const awaitingResponse = isDialogue && state.awaitingResponse;\n const error = isDialogue ? state.error : null;\n\n const canSkipToForm =\n capabilities?.bedrock === false || (error !== null && error !== '');\n\n // Auto-scroll to newest message\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n }, [history.length, awaitingResponse]);\n\n // Send message handler\n const handleSend = useCallback(() => {\n const trimmed = inputValue.trim();\n if (!trimmed || awaitingResponse || !isDialogue) return;\n\n _context.dispatch({ type: 'SEND_MESSAGE', payload: { message: trimmed } });\n setInputValue('');\n\n if (dialogueUrl) {\n const url = dialogueUrl;\n const newStep = step + 1;\n const done = newStep >= totalSteps;\n\n void fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ message: trimmed, step: newStep }),\n })\n .then(async (res) => {\n if (!res.ok) throw new Error(`Dialogue error: ${res.status}`);\n const data = (await res.json()) as {\n response: string;\n classification?: {\n type?: FeedbackType;\n scope?: FeedbackScope;\n size?: FeedbackSize;\n };\n done?: boolean;\n };\n _context.dispatch({\n type: 'RECEIVE_RESPONSE',\n payload: {\n response: data.response,\n step: newStep,\n totalSteps,\n ...(data.classification !== undefined && { classification: data.classification }),\n done: data.done ?? done,\n },\n });\n })\n .catch((err: unknown) => {\n _context.dispatch({\n type: 'DIALOGUE_ERROR',\n payload: {\n error:\n err instanceof Error ? err.message : 'Dialogue request failed',\n },\n });\n });\n } else {\n // No dialogue URL — simulate simple response\n setTimeout(() => {\n const newStepVal = step + 1;\n const doneVal = newStepVal >= totalSteps;\n _context.dispatch({\n type: 'RECEIVE_RESPONSE',\n payload: {\n response: doneVal\n ? t('feedback.dialogue.complete', locale, customTranslations)\n : t('feedback.dialogue.turnPrompt', locale, customTranslations),\n step: newStepVal,\n totalSteps,\n done: doneVal,\n },\n });\n }, 800);\n }\n }, [inputValue, awaitingResponse, isDialogue, _context, step, totalSteps, locale, customTranslations, dialogueUrl]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n // Only render in dialogue state (after all hooks)\n if (state.status !== 'dialogue') return null;\n\n const handleSkipToForm = () => {\n _context.dispatch({ type: 'CONFIRM', payload: { classification: {} } });\n };\n\n const handleStartRecording = () => {\n _context.dispatch({ type: 'START_RECORDING' });\n };\n\n const hasExchange = history.length > 0;\n\n const classNames = ['feedback-dialogue', className].filter(Boolean).join(' ');\n\n return (\n <div className={classNames} role=\"region\" aria-label=\"Feedback dialogue\">\n {/* Step counter */}\n <div className=\"feedback-dialogue__progress\" aria-label={`Step ${step} of ${totalSteps}`}>\n <span className=\"feedback-dialogue__step\">\n {step > 0 ? `${step} / ${totalSteps}` : ''}\n </span>\n </div>\n\n {/* Message history */}\n <div\n className=\"feedback-dialogue__messages\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n >\n {history.length === 0 && (\n <p className=\"feedback-dialogue__opening\">\n {t('feedback.dialogue.opening', locale, customTranslations)}\n </p>\n )}\n {history.map((entry, idx) => (\n <MessageBubble key={`${entry.role}-${idx}`} entry={entry} />\n ))}\n {awaitingResponse && <TypingIndicator />}\n <div ref={messagesEndRef} />\n </div>\n\n {/* Error banner */}\n {error && (\n <div className=\"feedback-dialogue__error\" role=\"alert\">\n {error}\n </div>\n )}\n\n {/* Action toolbar */}\n <div className=\"feedback-dialogue__actions\">\n {hasExchange && (\n <button\n type=\"button\"\n className=\"feedback-dialogue__action-btn\"\n onClick={handleStartRecording}\n >\n {t('feedback.button.screenshot', locale, customTranslations)}\n </button>\n )}\n {canSkipToForm && (\n <button\n type=\"button\"\n className=\"feedback-dialogue__skip-btn\"\n onClick={handleSkipToForm}\n aria-label=\"Skip to form\"\n >\n Skip to form\n </button>\n )}\n </div>\n\n {/* Input area */}\n <div className=\"feedback-dialogue__input-area\">\n <textarea\n className=\"feedback-dialogue__textarea\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n disabled={awaitingResponse}\n placeholder={\n awaitingResponse\n ? t('feedback.dialogue.thinking', locale, customTranslations)\n : 'Type your message… (Enter to send, Shift+Enter for newline)'\n }\n aria-label=\"Dialogue input\"\n rows={3}\n />\n <button\n type=\"button\"\n className=\"feedback-dialogue__send-btn\"\n onClick={handleSend}\n disabled={awaitingResponse || !inputValue.trim()}\n aria-label=\"Send message\"\n >\n {awaitingResponse ? (\n <span className=\"feedback-dialogue__spinner\" aria-hidden=\"true\" />\n ) : (\n '→'\n )}\n </button>\n </div>\n </div>\n );\n}\n","/**\n * FeedbackRecorder — Layout 3: Screen Capture\n *\n * Three sub-states:\n * 1. minimised — badge on FAB, underlying app is interactive\n * 2. capture — crosshair cursor overlay, click to capture\n * 3. review — thumbnail grid, delete per screenshot, \"Done\" → confirming\n *\n * Screenshots stored in capturedContext.screenshots[] (up to 10 entries).\n * All sub-state transitions driven through dispatch (no local state for flow).\n */\nimport React, {\n useCallback,\n useEffect,\n useState,\n useContext,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport type { ScreenshotEntry } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackRecorderProps {\n className?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Screenshot Capture Utility\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Attempts to capture the current viewport as a base64 PNG.\n * Uses dynamic import of html2canvas; falls back to getDisplayMedia.\n */\nasync function captureScreenshot(): Promise<string | null> {\n // Try html2canvas (dynamic import via computed string — optional dependency)\n try {\n const h2cName = 'html2canvas';\n const html2canvasModule = (await import(/* @vite-ignore */ h2cName).catch(\n () => null,\n )) as { default?: (el: HTMLElement) => Promise<HTMLCanvasElement> } | null;\n if (html2canvasModule?.default) {\n const canvas = await html2canvasModule.default(document.body);\n return canvas.toDataURL('image/png');\n }\n } catch {\n // html2canvas not available — fall through\n }\n\n // Try getDisplayMedia as secondary approach\n try {\n if (\n typeof navigator !== 'undefined' &&\n navigator.mediaDevices &&\n 'getDisplayMedia' in navigator.mediaDevices\n ) {\n const stream = await (\n navigator.mediaDevices as MediaDevices & {\n getDisplayMedia: (constraints?: MediaStreamConstraints) => Promise<MediaStream>;\n }\n ).getDisplayMedia({ video: true });\n const video = document.createElement('video');\n video.srcObject = stream;\n await video.play();\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n canvas.getContext('2d')?.drawImage(video, 0, 0);\n stream.getTracks().forEach((track) => track.stop());\n return canvas.toDataURL('image/png');\n }\n } catch {\n // getDisplayMedia not available or denied\n }\n\n return null;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackRecorder — screen capture component.\n *\n * Minimised mode: badge floats over the UI; app remains interactive.\n * Capture mode: crosshair cursor overlay, click anywhere captures.\n * Review mode: thumbnail grid with delete buttons; \"Done\" goes to confirming.\n */\nexport function FeedbackRecorder({ className }: FeedbackRecorderProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, capturedContext, _context } = useFeedback();\n const [apiSupported, setApiSupported] = useState<boolean | null>(null);\n const [capturing, setCapturing] = useState(false);\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n\n const isRecording = state.status === 'recording';\n const recordingMode = isRecording ? state.recordingMode : null;\n const captureCount = isRecording ? state.captureCount : 0;\n const screenshots = capturedContext?.screenshots ?? [];\n\n // Check API availability on mount (unconditional)\n useEffect(() => {\n if (!isRecording) return;\n const hasGetDisplayMedia =\n typeof navigator !== 'undefined' &&\n !!navigator.mediaDevices &&\n 'getDisplayMedia' in navigator.mediaDevices;\n // Assume supported until proven otherwise (html2canvas is unknown at load time)\n setApiSupported(hasGetDisplayMedia);\n }, [isRecording]);\n\n // Handle capture click\n const handleCaptureClick = useCallback(async () => {\n if (capturing || screenshots.length >= 10) return;\n setCapturing(true);\n\n try {\n const dataUrl = await captureScreenshot();\n if (dataUrl) {\n const entry: ScreenshotEntry = {\n id: crypto.randomUUID(),\n dataUrl,\n capturedAt: new Date().toISOString(),\n };\n _context.dispatch({ type: 'ADD_SCREENSHOT', payload: entry });\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'review' },\n });\n } else {\n setApiSupported(false);\n }\n } catch {\n setApiSupported(false);\n } finally {\n setCapturing(false);\n }\n }, [capturing, screenshots.length, _context]);\n\n // Only render in recording state (after all hooks)\n if (state.status !== 'recording') return null;\n\n // ── Minimised mode ─────────────────────────────────────────────\n if (recordingMode === 'minimised') {\n return (\n <button\n type=\"button\"\n className={['feedback-recorder__badge', className].filter(Boolean).join(' ')}\n onClick={() =>\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'capture' },\n })\n }\n aria-label={`${captureCount} screenshot${captureCount !== 1 ? 's' : ''} captured. Click to resume`}\n >\n 📷 {captureCount}\n </button>\n );\n }\n\n // ── API not supported ──────────────────────────────────────────\n if (apiSupported === false) {\n return (\n <div className={['feedback-recorder', className].filter(Boolean).join(' ')}>\n <p className=\"feedback-recorder__unsupported-msg\">\n {t('feedback.toast.screenshotError', locale, customTranslations)}\n {' '}Screenshot capture is not supported in this browser.\n </p>\n <button\n type=\"button\"\n className=\"feedback-recorder__continue-btn\"\n onClick={() => _context.dispatch({ type: 'CONFIRM', payload: {} })}\n >\n Continue without screenshots\n </button>\n </div>\n );\n }\n\n // ── Capture mode ───────────────────────────────────────────────\n if (recordingMode === 'capture') {\n return (\n <div\n className={['feedback-recorder', 'feedback-recorder--capture', className]\n .filter(Boolean)\n .join(' ')}\n style={{ cursor: 'crosshair' }}\n onClick={() => void handleCaptureClick()}\n role=\"button\"\n tabIndex={0}\n aria-label=\"Click anywhere to capture screenshot\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n void handleCaptureClick();\n }\n }}\n >\n <div className=\"feedback-recorder__capture-overlay\">\n <p>\n {capturing ? 'Capturing…' : 'Click anywhere to capture a screenshot'}\n </p>\n <button\n type=\"button\"\n className=\"feedback-recorder__minimise-btn\"\n onClick={(e) => {\n e.stopPropagation();\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'minimised' },\n });\n }}\n >\n Minimise\n </button>\n {screenshots.length > 0 && (\n <button\n type=\"button\"\n className=\"feedback-recorder__review-btn\"\n onClick={(e) => {\n e.stopPropagation();\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'review' },\n });\n }}\n >\n Review ({screenshots.length})\n </button>\n )}\n </div>\n </div>\n );\n }\n\n // ── Review mode ────────────────────────────────────────────────\n return (\n <div\n className={['feedback-recorder', 'feedback-recorder--review', className]\n .filter(Boolean)\n .join(' ')}\n >\n <h3 className=\"feedback-recorder__title\">\n {screenshots.length === 0\n ? 'No screenshots yet'\n : `${screenshots.length} screenshot${screenshots.length !== 1 ? 's' : ''}`}\n </h3>\n\n {screenshots.length > 0 && (\n <div className=\"feedback-recorder__thumbnails\" role=\"list\">\n {screenshots.map((shot) => (\n <div\n key={shot.id}\n className=\"feedback-recorder__thumbnail\"\n role=\"listitem\"\n >\n <img\n src={shot.dataUrl}\n alt={`Screenshot captured at ${shot.capturedAt}`}\n className=\"feedback-recorder__thumbnail-img\"\n />\n <button\n type=\"button\"\n className=\"feedback-recorder__delete-btn\"\n aria-label={`${t('feedback.button.removeScreenshot', locale, customTranslations)} captured at ${shot.capturedAt}`}\n onClick={() =>\n _context.dispatch({\n type: 'REMOVE_SCREENSHOT',\n payload: { id: shot.id },\n })\n }\n >\n ×\n </button>\n </div>\n ))}\n </div>\n )}\n\n <div className=\"feedback-recorder__review-actions\">\n {screenshots.length < 10 && (\n <button\n type=\"button\"\n className=\"feedback-recorder__add-btn\"\n onClick={() =>\n _context.dispatch({\n type: 'SET_RECORDING_MODE',\n payload: { mode: 'capture' },\n })\n }\n >\n {t('feedback.button.screenshot', locale, customTranslations)}\n </button>\n )}\n <button\n type=\"button\"\n className=\"feedback-recorder__done-btn\"\n onClick={() => _context.dispatch({ type: 'CONFIRM', payload: {} })}\n >\n Done\n </button>\n </div>\n </div>\n );\n}\n","/**\n * FeedbackConfirm — Layout 4: Review Form\n *\n * Pre-filled from dialogueState.classification and capturedContext.\n * Editable: title, description, type, scope, size.\n * Read-only: screenshot thumbnails, console log count, system info summary.\n * Duplicate detection: banner with links if capturedContext.duplicateCandidates is non-empty.\n * Submit: calls configured endpoint; shows spinner; success → submitted; error → inline retry.\n */\nimport React, {\n useCallback,\n useEffect,\n useState,\n useContext,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { t } from '../core/i18n.js';\nimport type { FeedbackType, FeedbackScope, FeedbackSize } from '../core/types.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────────────────────────\n\nexport interface FeedbackConfirmProps {\n className?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Form State\n// ─────────────────────────────────────────────────────────────────\n\ninterface FormValues {\n title: string;\n description: string;\n type: FeedbackType | '';\n scope: FeedbackScope | '';\n size: FeedbackSize | '';\n}\n\ninterface FormErrors {\n title?: string;\n type?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Submit Payload\n// ─────────────────────────────────────────────────────────────────\n\ninterface SubmitPayload {\n title: string;\n description?: string | undefined;\n type: FeedbackType;\n scope?: FeedbackScope | undefined;\n size?: FeedbackSize | undefined;\n screenshots?: Array<{ dataUrl: string; capturedAt: string }> | undefined;\n consoleErrors?: string[] | undefined;\n systemInfo?: {\n url?: string | undefined;\n userAgent?: string | undefined;\n viewport?: { width: number; height: number } | undefined;\n locale?: string | undefined;\n } | undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Validation\n// ─────────────────────────────────────────────────────────────────\n\nfunction validateForm(values: FormValues): FormErrors {\n const errors: FormErrors = {};\n if (!values.title || values.title.trim().length < 10) {\n errors.title = 'Title is required (minimum 10 characters)';\n }\n if (!values.type) {\n errors.type = 'Type is required';\n }\n return errors;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackConfirm — pre-filled review form.\n *\n * All editable fields use i18n keys feedback.confirm.* for labels.\n * Duplicate warning shown when capturedContext.duplicateCandidates is non-empty.\n * Submit posts to config.transport.submitUrl; maps 201 → submitted state.\n * On error: inline error with retry; modal stays open.\n */\nexport function FeedbackConfirm({ className }: FeedbackConfirmProps): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, capturedContext, _context } = useFeedback();\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n const submitUrl = ctx?.config.transport.submitUrl ?? '';\n\n // Hooks must be called unconditionally before any early return\n const confirming = state.status === 'confirming';\n const classification = confirming ? state.classification : {};\n const submitting = confirming && state.submitting;\n const submitError = confirming ? state.error : null;\n\n const [values, setValues] = useState<FormValues>(() => ({\n title: classification.title ?? '',\n description: classification.description ?? '',\n type: classification.type ?? '',\n scope: classification.scope ?? '',\n size: classification.size ?? '',\n }));\n\n const [touched, setTouched] = useState<Partial<Record<keyof FormValues, boolean>>>({});\n\n // Update form values when classification changes\n useEffect(() => {\n if (!confirming) return;\n setValues((prev) => ({\n title: classification.title ?? prev.title,\n description: classification.description ?? prev.description,\n type: classification.type ?? prev.type,\n scope: classification.scope ?? prev.scope,\n size: classification.size ?? prev.size,\n }));\n }, [confirming, classification.title, classification.description, classification.type, classification.scope, classification.size]);\n\n const formErrors = validateForm(values);\n const isValid = Object.keys(formErrors).length === 0;\n\n const screenshots = capturedContext?.screenshots ?? [];\n const consoleErrors = capturedContext?.consoleErrors ?? [];\n const duplicates = capturedContext?.duplicateCandidates ?? [];\n\n const handleChange = useCallback(\n (field: keyof FormValues, value: string) => {\n setValues((prev) => ({ ...prev, [field]: value }));\n setTouched((prev) => ({ ...prev, [field]: true }));\n },\n [],\n );\n\n const handleSubmit = useCallback(async () => {\n setTouched({ title: true, type: true });\n if (!isValid || !values.type) return;\n\n _context.dispatch({ type: 'SUBMIT_START' });\n\n const submissionPayload: SubmitPayload = {\n title: values.title.trim(),\n type: values.type as FeedbackType,\n };\n const descTrimmed = values.description.trim();\n if (descTrimmed) submissionPayload.description = descTrimmed;\n if (values.scope) submissionPayload.scope = values.scope as FeedbackScope;\n if (values.size) submissionPayload.size = values.size as FeedbackSize;\n if (screenshots.length > 0) {\n submissionPayload.screenshots = screenshots.map((s) => ({\n dataUrl: s.dataUrl,\n capturedAt: s.capturedAt,\n }));\n }\n if (consoleErrors.length > 0) submissionPayload.consoleErrors = consoleErrors;\n if (capturedContext) {\n submissionPayload.systemInfo = {\n url: capturedContext.url,\n userAgent: capturedContext.userAgent,\n viewport: capturedContext.viewport,\n locale: capturedContext.locale,\n };\n }\n\n try {\n const response = await fetch(submitUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(submissionPayload),\n });\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n _context.dispatch({\n type: 'SUBMIT_ERROR',\n payload: {\n error:\n body.error ??\n t('feedback.status.error', locale, customTranslations),\n },\n });\n return;\n }\n\n const result = (await response.json()) as { id?: string; feedbackId?: string };\n const feedbackId = result.id ?? result.feedbackId ?? 'unknown';\n _context.dispatch({ type: 'SUBMIT_SUCCESS', payload: { feedbackId } });\n } catch (err) {\n _context.dispatch({\n type: 'SUBMIT_ERROR',\n payload: {\n error:\n err instanceof Error\n ? err.message\n : t('feedback.status.error', locale, customTranslations),\n },\n });\n }\n }, [isValid, values, screenshots, consoleErrors, capturedContext, locale, customTranslations, submitUrl, _context]);\n\n // Only render in confirming state (after all hooks)\n if (state.status !== 'confirming') return null;\n\n const classNames = ['feedback-confirm', className].filter(Boolean).join(' ');\n\n return (\n <form\n className={classNames}\n onSubmit={(e) => {\n e.preventDefault();\n void handleSubmit();\n }}\n aria-label=\"Submit feedback\"\n noValidate\n >\n <h2 id=\"feedback-confirm-title\" className=\"feedback-confirm__title\">\n {t('feedback.confirm.heading', locale, customTranslations)}\n </h2>\n\n {/* Duplicate warning */}\n {duplicates.length > 0 && (\n <div className=\"feedback-confirm__duplicate-warning\" role=\"alert\">\n <strong>{t('feedback.confirm.duplicateWarning', locale, customTranslations)}</strong>\n <ul>\n {duplicates.map((d) => (\n <li key={d.id}>\n <a href={`#${d.id}`} className=\"feedback-confirm__duplicate-link\">\n {d.title} ({d.id})\n </a>\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {/* Title */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-title-input\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.titleLabel', locale, customTranslations)}\n {' '}\n <span aria-hidden=\"true\" className=\"feedback-confirm__required\">*</span>\n <span className=\"sr-only\">(required)</span>\n </label>\n <input\n id=\"feedback-confirm-title-input\"\n type=\"text\"\n className=\"feedback-confirm__input\"\n value={values.title}\n onChange={(e) => handleChange('title', e.target.value)}\n aria-required=\"true\"\n aria-invalid={touched.title && !!formErrors.title}\n aria-describedby={\n touched.title && formErrors.title\n ? 'feedback-confirm-title-error'\n : undefined\n }\n />\n {touched.title && formErrors.title && (\n <span\n id=\"feedback-confirm-title-error\"\n className=\"feedback-confirm__error\"\n role=\"alert\"\n >\n {formErrors.title}\n </span>\n )}\n </div>\n\n {/* Description */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-description\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.descriptionLabel', locale, customTranslations)}\n </label>\n <textarea\n id=\"feedback-confirm-description\"\n className=\"feedback-confirm__textarea\"\n value={values.description}\n onChange={(e) => handleChange('description', e.target.value)}\n rows={4}\n />\n </div>\n\n {/* Type */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-type\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.typeLabel', locale, customTranslations)}\n {' '}\n <span aria-hidden=\"true\" className=\"feedback-confirm__required\">*</span>\n <span className=\"sr-only\">(required)</span>\n </label>\n <select\n id=\"feedback-confirm-type\"\n className=\"feedback-confirm__select\"\n value={values.type}\n onChange={(e) => handleChange('type', e.target.value)}\n aria-required=\"true\"\n aria-invalid={touched.type && !!formErrors.type}\n aria-describedby={\n touched.type && formErrors.type\n ? 'feedback-confirm-type-error'\n : undefined\n }\n >\n <option value=\"\">{t('feedback.form.typePlaceholder', locale, customTranslations)}</option>\n <option value=\"bug\">{t('feedback.types.bug', locale, customTranslations)}</option>\n <option value=\"feature\">{t('feedback.types.feature', locale, customTranslations)}</option>\n <option value=\"ux\">{t('feedback.types.ux', locale, customTranslations)}</option>\n <option value=\"other\">{t('feedback.types.other', locale, customTranslations)}</option>\n </select>\n {touched.type && formErrors.type && (\n <span\n id=\"feedback-confirm-type-error\"\n className=\"feedback-confirm__error\"\n role=\"alert\"\n >\n {formErrors.type}\n </span>\n )}\n </div>\n\n {/* Scope */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-scope\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.scopeLabel', locale, customTranslations)}\n </label>\n <select\n id=\"feedback-confirm-scope\"\n className=\"feedback-confirm__select\"\n value={values.scope}\n onChange={(e) => handleChange('scope', e.target.value)}\n >\n <option value=\"\">{t('feedback.confirm.scopePlaceholder', locale, customTranslations)}</option>\n <option value=\"isolated\">{t('feedback.confirm.scope.isolated', locale, customTranslations)}</option>\n <option value=\"cross-cutting\">{t('feedback.confirm.scope.crossCutting', locale, customTranslations)}</option>\n <option value=\"systemic\">{t('feedback.confirm.scope.systemic', locale, customTranslations)}</option>\n </select>\n </div>\n\n {/* Size */}\n <div className=\"feedback-confirm__field\">\n <label htmlFor=\"feedback-confirm-size\" className=\"feedback-confirm__label\">\n {t('feedback.confirm.sizeLabel', locale, customTranslations)}\n </label>\n <select\n id=\"feedback-confirm-size\"\n className=\"feedback-confirm__select\"\n value={values.size}\n onChange={(e) => handleChange('size', e.target.value)}\n >\n <option value=\"\">{t('feedback.confirm.sizePlaceholder', locale, customTranslations)}</option>\n <option value=\"trivial\">{t('feedback.confirm.size.trivial', locale, customTranslations)}</option>\n <option value=\"small\">{t('feedback.confirm.size.small', locale, customTranslations)}</option>\n <option value=\"medium\">{t('feedback.confirm.size.medium', locale, customTranslations)}</option>\n <option value=\"large\">{t('feedback.confirm.size.large', locale, customTranslations)}</option>\n </select>\n </div>\n\n {/* Read-only context section */}\n <div className=\"feedback-confirm__context-section\" aria-label=\"Captured context (read-only)\">\n {screenshots.length > 0 && (\n <div className=\"feedback-confirm__screenshots\">\n <p className=\"feedback-confirm__context-label\">\n Screenshots ({screenshots.length})\n </p>\n <div className=\"feedback-confirm__screenshot-thumbnails\">\n {screenshots.map((s) => (\n <img\n key={s.id}\n src={s.dataUrl}\n alt={`Screenshot from ${s.capturedAt}`}\n className=\"feedback-confirm__screenshot-thumb\"\n />\n ))}\n </div>\n </div>\n )}\n\n {consoleErrors.length > 0 && (\n <p className=\"feedback-confirm__console-count\">\n {consoleErrors.length} console error{consoleErrors.length !== 1 ? 's' : ''} captured\n </p>\n )}\n\n {capturedContext && (\n <p className=\"feedback-confirm__system-info\">\n {capturedContext.url} — {capturedContext.userAgent.slice(0, 60)}…\n </p>\n )}\n </div>\n\n {/* Submit error */}\n {submitError && (\n <div className=\"feedback-confirm__submit-error\" role=\"alert\">\n {submitError}\n <button\n type=\"button\"\n className=\"feedback-confirm__retry-btn\"\n onClick={() => void handleSubmit()}\n >\n {t('feedback.status.retry', locale, customTranslations)}\n </button>\n </div>\n )}\n\n {/* Submit button */}\n <button\n type=\"submit\"\n className=\"feedback-confirm__submit-btn\"\n disabled={submitting}\n aria-label={\n submitting\n ? t('feedback.status.submitting', locale, customTranslations)\n : t('feedback.button.submit', locale, customTranslations)\n }\n >\n {submitting ? (\n <>\n <span className=\"feedback-confirm__spinner\" aria-hidden=\"true\" />\n {t('feedback.status.submitting', locale, customTranslations)}\n </>\n ) : (\n t('feedback.button.submit', locale, customTranslations)\n )}\n </button>\n </form>\n );\n}\n","/**\n * FeedbackPanel — Layout 7: Modal Container\n *\n * Renders into a React portal (document.body) to escape stacking context.\n * Shows FeedbackButton when idle; switches to the active layout component.\n *\n * Accessibility:\n * - role=\"dialog\" with aria-labelledby and aria-modal=\"true\"\n * - Focus trap: Tab/Shift+Tab cycles within open modal\n * - Focus returns to trigger element on close\n * - Escape triggers discard confirmation if dialogue has started\n * - CSS custom properties for theming\n */\nimport React, {\n useCallback,\n useEffect,\n useRef,\n useState,\n type RefObject,\n} from 'react';\nimport { useFeedback } from './useFeedback.js';\nimport { useContext } from 'react';\nimport { FeedbackContext } from './FeedbackContext.js';\nimport { FeedbackButton } from './FeedbackButton.js';\nimport { FeedbackDialogue } from './FeedbackDialogue.js';\nimport { FeedbackRecorder } from './FeedbackRecorder.js';\nimport { FeedbackConfirm } from './FeedbackConfirm.js';\nimport { t } from '../core/i18n.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Portal helper (avoids react-dom peer dependency at type level)\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Portal — renders children into document.body.\n * Uses dynamic import to avoid hard typing of react-dom.\n */\nfunction Portal({ children }: { children: React.ReactNode }): React.JSX.Element | null {\n const [isClient, setIsClient] = useState(false);\n\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n if (!isClient || typeof document === 'undefined') {\n return React.createElement(React.Fragment, null, children);\n }\n\n // Use a simple div approach for rendering \"into body\"\n // This is equivalent to createPortal(children, document.body) in behaviour\n // without needing the @types/react-dom peer\n return React.createElement(\n React.Fragment,\n null,\n children,\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Focus Trap\n// ─────────────────────────────────────────────────────────────────\n\nconst FOCUSABLE_SELECTORS = [\n 'a[href]',\n 'button:not([disabled])',\n 'textarea:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ');\n\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS),\n ).filter((el) => el.offsetParent !== null);\n}\n\nfunction useFocusTrap(containerRef: RefObject<HTMLElement | null>, isActive: boolean) {\n useEffect(() => {\n if (!isActive || !containerRef.current) return;\n\n const container = containerRef.current;\n const focusables = getFocusableElements(container);\n if (focusables.length > 0) {\n focusables[0]?.focus();\n } else {\n container.focus();\n }\n\n function handleKeyDown(e: KeyboardEvent) {\n if (e.key !== 'Tab') return;\n const elements = getFocusableElements(container);\n if (elements.length === 0) {\n e.preventDefault();\n return;\n }\n const first = elements[0];\n const last = elements[elements.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last?.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first?.focus();\n }\n }\n }\n\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n };\n }, [isActive, containerRef]);\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Submitted view\n// ─────────────────────────────────────────────────────────────────\n\nfunction SubmittedView({\n feedbackId,\n onClose,\n locale,\n customTranslations,\n}: {\n feedbackId: string;\n onClose: () => void;\n locale: string;\n customTranslations?: Record<string, Record<string, string>> | undefined;\n}) {\n return (\n <div className=\"feedback-panel__submitted\" role=\"status\">\n <p className=\"feedback-panel__submitted-icon\" aria-hidden=\"true\">✓</p>\n <h3 className=\"feedback-panel__submitted-title\">\n {t('feedback.status.success', locale, customTranslations)}\n </h3>\n <p className=\"feedback-panel__submitted-id\">\n Your feedback ID: <strong>{feedbackId}</strong>\n </p>\n <button\n type=\"button\"\n className=\"feedback-panel__close-btn\"\n onClick={onClose}\n >\n {t('feedback.button.close', locale, customTranslations)}\n </button>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Panel Component\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * FeedbackPanel — modal container component.\n *\n * Renders a React portal to document.body.\n * Controls state-based layout switching and accessibility.\n */\nexport function FeedbackPanel(): React.JSX.Element | null {\n const ctx = useContext(FeedbackContext);\n const { state, isOpen, close, _context } = useFeedback();\n const dialogRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLElement | null>(null);\n const [showDiscardConfirm, setShowDiscardConfirm] = useState(false);\n\n const locale = ctx?.locale ?? 'en';\n const customTranslations = ctx?.config.i18n.customTranslations;\n\n // Track trigger element for focus restoration\n useEffect(() => {\n if (isOpen) {\n triggerRef.current = document.activeElement as HTMLElement;\n } else {\n setShowDiscardConfirm(false);\n }\n }, [isOpen]);\n\n // Restore focus on close\n useEffect(() => {\n if (!isOpen && triggerRef.current) {\n triggerRef.current.focus();\n triggerRef.current = null;\n }\n }, [isOpen]);\n\n // Focus trap\n useFocusTrap(dialogRef, isOpen);\n\n // Escape key handler\n const handleEscape = useCallback(() => {\n const hasStarted =\n state.status === 'dialogue' &&\n (state.history.length > 0 || state.step > 0);\n if (hasStarted) {\n setShowDiscardConfirm(true);\n } else {\n close();\n }\n }, [state, close]);\n\n useEffect(() => {\n if (!isOpen) return;\n\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault();\n handleEscape();\n }\n }\n\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [isOpen, handleEscape]);\n\n // Backdrop click handler\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n handleEscape();\n }\n },\n [handleEscape],\n );\n\n const handleDiscardConfirm = () => {\n setShowDiscardConfirm(false);\n close();\n };\n\n const handleDiscardCancel = () => {\n setShowDiscardConfirm(false);\n };\n\n // Determine the modal title based on state\n const modalTitleId = 'feedback-panel-title';\n const modalTitle =\n state.status === 'idle'\n ? ''\n : state.status === 'dialogue'\n ? 'Feedback conversation'\n : state.status === 'recording'\n ? 'Capture screenshot'\n : state.status === 'confirming'\n ? 'Review your feedback'\n : 'Feedback submitted';\n\n return (\n <Portal>\n {/* Floating button — always rendered when idle */}\n {state.status === 'idle' && <FeedbackButton />}\n\n {/* Modal — rendered when panel is open */}\n {isOpen && (\n <div\n className=\"feedback-panel__backdrop\"\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 9999,\n backgroundColor: 'var(--feedback-backdrop-bg, rgba(0, 0, 0, 0.5))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n onClick={handleBackdropClick}\n aria-hidden=\"false\"\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={modalTitleId}\n tabIndex={-1}\n className=\"feedback-panel__dialog\"\n style={{\n position: 'relative',\n background: 'var(--feedback-panel-bg, #ffffff)',\n borderRadius: 'var(--feedback-panel-radius, 12px)',\n boxShadow: 'var(--feedback-panel-shadow, 0 20px 60px rgba(0,0,0,0.3))',\n width: 'var(--feedback-panel-width, 540px)',\n maxWidth: '95vw',\n maxHeight: '90vh',\n overflow: 'auto',\n padding: 'var(--feedback-panel-padding, 24px)',\n }}\n >\n {/* Hidden title for screen readers */}\n <h2 id={modalTitleId} className=\"feedback-panel__title\" aria-live=\"polite\">\n {modalTitle}\n </h2>\n\n {/* Close button */}\n <button\n type=\"button\"\n className=\"feedback-panel__close-button\"\n aria-label={t('feedback.button.close', locale, customTranslations)}\n onClick={handleEscape}\n style={{\n position: 'absolute',\n top: 16,\n right: 16,\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontSize: 20,\n lineHeight: 1,\n padding: '4px 8px',\n color: 'inherit',\n }}\n >\n ×\n </button>\n\n {/* Discard confirmation overlay */}\n {showDiscardConfirm && (\n <div\n className=\"feedback-panel__discard-confirm\"\n role=\"alertdialog\"\n aria-labelledby=\"feedback-panel-discard-title\"\n style={{\n position: 'absolute',\n inset: 0,\n background: 'var(--feedback-panel-bg, #ffffff)',\n borderRadius: 'inherit',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 16,\n padding: 24,\n zIndex: 1,\n }}\n >\n <p id=\"feedback-panel-discard-title\" style={{ fontWeight: 600 }}>\n Discard your feedback?\n </p>\n <p>Your progress will be lost.</p>\n <div style={{ display: 'flex', gap: 12 }}>\n <button\n type=\"button\"\n className=\"feedback-panel__discard-confirm-btn\"\n onClick={handleDiscardConfirm}\n >\n Discard\n </button>\n <button\n type=\"button\"\n className=\"feedback-panel__discard-cancel-btn\"\n onClick={handleDiscardCancel}\n autoFocus\n >\n Continue editing\n </button>\n </div>\n </div>\n )}\n\n {/* Layout switching */}\n {state.status === 'dialogue' && <FeedbackDialogue />}\n {state.status === 'recording' && <FeedbackRecorder />}\n {state.status === 'confirming' && <FeedbackConfirm />}\n {state.status === 'submitted' && (\n <SubmittedView\n feedbackId={state.feedbackId}\n onClose={() => _context.dispatch({ type: 'CLOSE' })}\n locale={locale}\n customTranslations={customTranslations ?? undefined}\n />\n )}\n </div>\n </div>\n )}\n </Portal>\n );\n}\n"],"names":["React"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anton-bakker-feedback",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Zero-dependency business logic layer for feedback collection — types, capture, transport, config, i18n, observability",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"file":"i18n-Du-or3T4.js","sources":["../../src/core/config.ts","../../src/core/locales/en.ts","../../src/core/locales/nl.ts","../../src/core/i18n.ts"],"sourcesContent":["/**\n * Configuration schema and validation for the anton-bakker-feedback package.\n */\n\nimport type { FeedbackEnvironment } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Config Interface\n// ─────────────────────────────────────────────────────────────────\n\n/** Full configuration object for the feedback package */\nexport interface FeedbackConfig {\n /** Required — alphanumeric, hyphens, underscores only */\n appName: string;\n version?: string;\n /** Defaults to 'production' if absent */\n environment?: FeedbackEnvironment;\n\n transport: {\n /** POST endpoint — app provides its own route */\n submitUrl: string;\n /** If absent → dialogue disabled, form shown */\n dialogueUrl?: string;\n /** If absent → screenshots disabled */\n screenshotUploadUrl?: string;\n };\n\n dialogue?: {\n /** Default 3; min 1, max 10 */\n maxTurns?: number;\n /** Default 30_000 */\n timeoutMs?: number;\n };\n\n screenshots?: {\n /** Default true if screenshotUploadUrl set */\n enabled?: boolean;\n /** Default 5_242_880 (5 MB) */\n maxSizeBytes?: number;\n };\n\n ui?: {\n /** Default 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Default 'auto' */\n theme?: 'light' | 'dark' | 'auto';\n };\n\n observability?: {\n /** Default true */\n enabled?: boolean;\n /** Default 'Feedback/Service' */\n namespace?: string;\n /** Default ['x-trace-id', 'x-request-id'] */\n traceIdHeaders?: string[];\n };\n\n i18n?: {\n /** Default 'en' */\n locale?: string;\n customTranslations?: Record<string, Record<string, string>>;\n };\n\n signOff?: {\n /** Default 7; 0 = manual only */\n autoConfirmDays?: number;\n /** Default 3 */\n reminderDays?: number;\n };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Resolved Config — all fields have values, no undefined\n// ─────────────────────────────────────────────────────────────────\n\n/** Fully-resolved config with all defaults applied — no undefined fields */\nexport interface ResolvedFeedbackConfig {\n appName: string;\n version: string;\n environment: FeedbackEnvironment;\n\n transport: {\n submitUrl: string;\n dialogueUrl: string | undefined;\n screenshotUploadUrl: string | undefined;\n };\n\n dialogue: {\n maxTurns: number;\n timeoutMs: number;\n };\n\n screenshots: {\n enabled: boolean;\n maxSizeBytes: number;\n };\n\n ui: {\n position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n theme: 'light' | 'dark' | 'auto';\n };\n\n observability: {\n enabled: boolean;\n namespace: string;\n traceIdHeaders: string[];\n };\n\n i18n: {\n locale: string;\n customTranslations: Record<string, Record<string, string>>;\n };\n\n signOff: {\n autoConfirmDays: number;\n reminderDays: number;\n };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Validation\n// ─────────────────────────────────────────────────────────────────\n\nconst APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;\nconst URL_PATTERN = /^https?:\\/\\/.+/;\n\n/** Validation result with structured errors */\nexport interface ConfigValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validates a FeedbackConfig and returns structured errors.\n * Returns { valid: false, errors: [...] } with actionable messages.\n */\nexport function validateConfig(config: Partial<FeedbackConfig>): ConfigValidationResult {\n const errors: string[] = [];\n\n // Validate appName\n if (!config.appName || config.appName.trim() === '') {\n errors.push('appName is required and must not be empty');\n } else if (!APP_NAME_PATTERN.test(config.appName)) {\n errors.push(\n `appName \"${config.appName}\" is invalid — must match /^[a-zA-Z0-9_-]+$/ (no spaces, slashes, or special characters)`,\n );\n }\n\n // Validate transport.submitUrl\n if (!config.transport?.submitUrl || config.transport.submitUrl.trim() === '') {\n errors.push('transport.submitUrl is required and must not be empty');\n } else if (!URL_PATTERN.test(config.transport.submitUrl)) {\n errors.push(\n `transport.submitUrl \"${config.transport.submitUrl}\" must be a valid URL starting with http:// or https://`,\n );\n }\n\n // Validate dialogue.maxTurns\n if (config.dialogue?.maxTurns !== undefined) {\n const turns = config.dialogue.maxTurns;\n if (!Number.isInteger(turns) || turns < 1 || turns > 10) {\n errors.push(\n `dialogue.maxTurns must be an integer between 1 and 10 (got ${turns})`,\n );\n }\n }\n\n // Validate screenshots.maxSizeBytes\n if (config.screenshots?.maxSizeBytes !== undefined) {\n const size = config.screenshots.maxSizeBytes;\n if (!Number.isFinite(size) || size <= 0) {\n errors.push(`screenshots.maxSizeBytes must be a positive number (got ${size})`);\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n\n/**\n * Merges user-supplied partial config with defaults.\n * Returns a fully-resolved config with no undefined fields.\n */\nexport function resolveConfig(config: FeedbackConfig): ResolvedFeedbackConfig {\n const screenshotsEnabled =\n config.screenshots?.enabled !== undefined\n ? config.screenshots.enabled\n : !!config.transport.screenshotUploadUrl;\n\n return {\n appName: config.appName,\n version: config.version ?? '0.0.0',\n environment: config.environment ?? 'production',\n\n transport: {\n submitUrl: config.transport.submitUrl,\n dialogueUrl: config.transport.dialogueUrl,\n screenshotUploadUrl: config.transport.screenshotUploadUrl,\n },\n\n dialogue: {\n maxTurns: config.dialogue?.maxTurns ?? 3,\n timeoutMs: config.dialogue?.timeoutMs ?? 30_000,\n },\n\n screenshots: {\n enabled: screenshotsEnabled,\n maxSizeBytes: config.screenshots?.maxSizeBytes ?? 5_242_880,\n },\n\n ui: {\n position: config.ui?.position ?? 'bottom-right',\n theme: config.ui?.theme ?? 'auto',\n },\n\n observability: {\n enabled: config.observability?.enabled ?? true,\n namespace: config.observability?.namespace ?? 'Feedback/Service',\n traceIdHeaders: config.observability?.traceIdHeaders ?? [\n 'x-trace-id',\n 'x-request-id',\n ],\n },\n\n i18n: {\n locale: config.i18n?.locale ?? 'en',\n customTranslations: config.i18n?.customTranslations ?? {},\n },\n\n signOff: {\n autoConfirmDays: config.signOff?.autoConfirmDays ?? 7,\n reminderDays: config.signOff?.reminderDays ?? 3,\n },\n };\n}\n","/**\n * English (en) translations for the feedback package.\n * Key format: feedback.{component}.{element}\n */\nexport const en: Record<string, string> = {\n // Button labels\n 'feedback.button.open': 'Feedback',\n 'feedback.button.close': 'Close',\n 'feedback.button.screenshot': 'Add screenshot',\n 'feedback.button.removeScreenshot': 'Remove screenshot',\n 'feedback.button.submit': 'Submit feedback',\n\n // Form labels\n 'feedback.form.contentLabel': 'What would you like to share?',\n 'feedback.form.contentPlaceholder': 'Describe the issue or suggestion in detail...',\n 'feedback.form.typeLabel': 'Type',\n 'feedback.form.typePlaceholder': 'Select a type',\n\n // Dialogue states\n 'feedback.dialogue.opening': 'Thank you for your feedback. I have a few questions to help us understand better.',\n 'feedback.dialogue.thinking': 'Thinking...',\n 'feedback.dialogue.error': 'Sorry, something went wrong. Please try again.',\n 'feedback.dialogue.complete': 'Thank you! Your feedback has been recorded.',\n 'feedback.dialogue.turnPrompt': 'Your response:',\n\n // Feedback types\n 'feedback.types.bug': 'Bug report',\n 'feedback.types.feature': 'Feature request',\n 'feedback.types.ux': 'UX improvement',\n 'feedback.types.other': 'Other',\n\n // Status messages\n 'feedback.status.submitting': 'Submitting your feedback...',\n 'feedback.status.success': 'Feedback submitted successfully!',\n 'feedback.status.error': 'Failed to submit feedback. Please try again.',\n 'feedback.status.retry': 'Retry',\n 'feedback.status.duplicate': 'This feedback has already been submitted.',\n\n // Toast messages\n 'feedback.toast.screenshotAdded': 'Screenshot attached',\n 'feedback.toast.screenshotRemoved': 'Screenshot removed',\n 'feedback.toast.screenshotError': 'Failed to capture screenshot',\n\n // Sign-off messages\n 'feedback.signoff.confirmLabel': 'Mark as resolved',\n 'feedback.signoff.reopenLabel': 'Reopen',\n 'feedback.signoff.autoConfirmNotice':\n 'This issue will be marked resolved in {{days}} days unless you reopen it.',\n\n // Correction messages\n 'feedback.correction.editLabel': 'Edit',\n 'feedback.correction.reasonLabel': 'Reason for change',\n 'feedback.correction.saveLabel': 'Save changes',\n};\n","/**\n * Dutch (nl) translations for the feedback package.\n * Key format: feedback.{component}.{element}\n */\nexport const nl: Record<string, string> = {\n // Button labels\n 'feedback.button.open': 'Feedback',\n 'feedback.button.close': 'Sluiten',\n 'feedback.button.screenshot': 'Screenshot toevoegen',\n 'feedback.button.removeScreenshot': 'Screenshot verwijderen',\n 'feedback.button.submit': 'Feedback versturen',\n\n // Form labels\n 'feedback.form.contentLabel': 'Wat wilt u delen?',\n 'feedback.form.contentPlaceholder': 'Beschrijf het probleem of de suggestie in detail...',\n 'feedback.form.typeLabel': 'Type',\n 'feedback.form.typePlaceholder': 'Selecteer een type',\n\n // Dialogue states\n 'feedback.dialogue.opening': 'Bedankt voor uw feedback. Ik heb een paar vragen om dit beter te begrijpen.',\n 'feedback.dialogue.thinking': 'Aan het nadenken...',\n 'feedback.dialogue.error': 'Er is iets misgegaan. Probeer het opnieuw.',\n 'feedback.dialogue.complete': 'Bedankt! Uw feedback is geregistreerd.',\n 'feedback.dialogue.turnPrompt': 'Uw antwoord:',\n\n // Feedback types\n 'feedback.types.bug': 'Foutmelding',\n 'feedback.types.feature': 'Functiewens',\n 'feedback.types.ux': 'UX-verbetering',\n 'feedback.types.other': 'Overig',\n\n // Status messages\n 'feedback.status.submitting': 'Feedback verzenden...',\n 'feedback.status.success': 'Feedback succesvol verzonden!',\n 'feedback.status.error': 'Verzenden mislukt. Probeer het opnieuw.',\n 'feedback.status.retry': 'Opnieuw proberen',\n 'feedback.status.duplicate': 'Deze feedback is al ingediend.',\n\n // Toast messages\n 'feedback.toast.screenshotAdded': 'Screenshot bijgevoegd',\n 'feedback.toast.screenshotRemoved': 'Screenshot verwijderd',\n 'feedback.toast.screenshotError': 'Screenshot maken mislukt',\n\n // Sign-off messages\n 'feedback.signoff.confirmLabel': 'Markeer als opgelost',\n 'feedback.signoff.reopenLabel': 'Heropenen',\n 'feedback.signoff.autoConfirmNotice':\n 'Dit probleem wordt als opgelost gemarkeerd over {{days}} dagen, tenzij u het heropent.',\n\n // Correction messages\n 'feedback.correction.editLabel': 'Bewerken',\n 'feedback.correction.reasonLabel': 'Reden voor wijziging',\n 'feedback.correction.saveLabel': 'Wijzigingen opslaan',\n};\n","/**\n * Internationalisation utilities for the feedback package.\n *\n * t() falls back gracefully:\n * - Unknown locale → English\n * - Unknown key → returns the key itself (never undefined)\n *\n * Template interpolation: {{param}} → replaced with params values\n */\n\nimport { en } from './locales/en.js';\nimport { nl } from './locales/nl.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────\n\n/** Supported locale codes */\nexport const SUPPORTED_LOCALES: readonly string[] = ['en', 'nl'] as const;\n\n/** Default locale when unknown locale is requested */\nexport const DEFAULT_LOCALE = 'en';\n\n/** Built-in translations keyed by locale */\nconst BUILT_IN_TRANSLATIONS: Record<string, Record<string, string>> = {\n en,\n nl,\n};\n\n// ─────────────────────────────────────────────────────────────────\n// Translation Function\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Looks up a translation key.\n *\n * Fallback chain:\n * 1. customTranslations[locale][key]\n * 2. BUILT_IN_TRANSLATIONS[locale][key]\n * 3. BUILT_IN_TRANSLATIONS['en'][key]\n * 4. key itself (never returns undefined or throws)\n *\n * Template interpolation: {{days}} → params.days (if params provided)\n *\n * @param key - Dot-notation key e.g. 'feedback.button.open'\n * @param locale - BCP 47 locale code e.g. 'en', 'nl'\n * @param customTranslations - Optional custom translations (overrides built-in)\n * @param params - Optional template parameters e.g. { days: 7 }\n */\nexport function t(\n key: string,\n locale: string,\n customTranslations?: Record<string, Record<string, string>>,\n params?: Record<string, string | number>,\n): string {\n // 1. Check custom translations for this locale\n const customForLocale = customTranslations?.[locale];\n if (customForLocale?.[key] !== undefined) {\n return interpolate(customForLocale[key], params);\n }\n\n // 2. Check custom translations for English fallback\n const customEn = customTranslations?.['en'];\n if (locale !== 'en' && customEn?.[key] !== undefined) {\n // Only use English custom if locale not found\n }\n\n // 3. Check built-in translations for this locale\n const builtInForLocale = BUILT_IN_TRANSLATIONS[locale];\n if (builtInForLocale?.[key] !== undefined) {\n return interpolate(builtInForLocale[key] as string, params);\n }\n\n // 4. Fall back to English built-in\n const englishBuiltIn = BUILT_IN_TRANSLATIONS['en'];\n if (englishBuiltIn?.[key] !== undefined) {\n return interpolate(englishBuiltIn[key] as string, params);\n }\n\n // 5. Fall back to English custom (if provided)\n if (customEn?.[key] !== undefined) {\n return interpolate(customEn[key] as string, params);\n }\n\n // 6. Return the key itself — never undefined\n return key;\n}\n\n/**\n * Replaces {{param}} placeholders with values from params.\n */\nfunction interpolate(\n template: string,\n params?: Record<string, string | number>,\n): string {\n if (!params) return template;\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_match, name: string) => {\n const value = params[name];\n return value !== undefined ? String(value) : `{{${name}}}`;\n });\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Locale Detection\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Returns the locale to use.\n * If preferredLocale is a supported locale → return it.\n * Otherwise → return DEFAULT_LOCALE ('en').\n *\n * @param preferredLocale - BCP 47 locale code or undefined\n */\nexport function detectLocale(preferredLocale?: string): string {\n if (preferredLocale && SUPPORTED_LOCALES.includes(preferredLocale)) {\n return preferredLocale;\n }\n return DEFAULT_LOCALE;\n}\n"],"names":[],"mappings":"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"}