anton-bakker-feedback 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FeedbackPanel.d.ts","sourceRoot":"","sources":["../../src/react/FeedbackPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAMN,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FeedbackPanel.d.ts","sourceRoot":"","sources":["../../src/react/FeedbackPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAMN,MAAM,OAAO,CAAC;AA2Xf;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,CA8NxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeedbackPanel.styles.test.d.ts","sourceRoot":"","sources":["../../../src/react/__tests__/FeedbackPanel.styles.test.tsx"],"names":[],"mappings":""}
|
package/dist/react.js
CHANGED
|
@@ -1160,6 +1160,233 @@ function FeedbackConfirm({ className }) {
|
|
|
1160
1160
|
}
|
|
1161
1161
|
);
|
|
1162
1162
|
}
|
|
1163
|
+
const PANEL_STYLES = `
|
|
1164
|
+
.feedback-panel__title {
|
|
1165
|
+
margin: 0 0 16px;
|
|
1166
|
+
padding-right: 32px;
|
|
1167
|
+
font-size: 18px;
|
|
1168
|
+
font-weight: 700;
|
|
1169
|
+
line-height: 1.3;
|
|
1170
|
+
color: var(--feedback-text, #111827);
|
|
1171
|
+
}
|
|
1172
|
+
.feedback-panel__close-button:hover {
|
|
1173
|
+
color: var(--feedback-text, #111827);
|
|
1174
|
+
opacity: 0.7;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/* ── Confirm form ───────────────────────────────────────────── */
|
|
1178
|
+
.feedback-confirm {
|
|
1179
|
+
display: flex;
|
|
1180
|
+
flex-direction: column;
|
|
1181
|
+
gap: 16px;
|
|
1182
|
+
}
|
|
1183
|
+
.feedback-confirm__title {
|
|
1184
|
+
margin: 0;
|
|
1185
|
+
font-size: 15px;
|
|
1186
|
+
font-weight: 600;
|
|
1187
|
+
color: var(--feedback-text-muted, #6b7280);
|
|
1188
|
+
}
|
|
1189
|
+
.feedback-confirm__field {
|
|
1190
|
+
display: flex;
|
|
1191
|
+
flex-direction: column;
|
|
1192
|
+
gap: 6px;
|
|
1193
|
+
}
|
|
1194
|
+
.feedback-confirm__label {
|
|
1195
|
+
font-size: 13px;
|
|
1196
|
+
font-weight: 600;
|
|
1197
|
+
color: var(--feedback-text, #374151);
|
|
1198
|
+
}
|
|
1199
|
+
.feedback-confirm__required {
|
|
1200
|
+
color: var(--feedback-danger, #dc2626);
|
|
1201
|
+
}
|
|
1202
|
+
.feedback-confirm__input,
|
|
1203
|
+
.feedback-confirm__textarea,
|
|
1204
|
+
.feedback-confirm__select {
|
|
1205
|
+
width: 100%;
|
|
1206
|
+
box-sizing: border-box;
|
|
1207
|
+
padding: 9px 12px;
|
|
1208
|
+
font-family: inherit;
|
|
1209
|
+
font-size: 14px;
|
|
1210
|
+
line-height: 1.4;
|
|
1211
|
+
color: var(--feedback-text, #111827);
|
|
1212
|
+
background: var(--feedback-input-bg, #ffffff);
|
|
1213
|
+
border: 1px solid var(--feedback-border, #d1d5db);
|
|
1214
|
+
border-radius: var(--feedback-input-radius, 8px);
|
|
1215
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
1216
|
+
}
|
|
1217
|
+
.feedback-confirm__textarea {
|
|
1218
|
+
min-height: 88px;
|
|
1219
|
+
resize: vertical;
|
|
1220
|
+
}
|
|
1221
|
+
.feedback-confirm__input:focus,
|
|
1222
|
+
.feedback-confirm__textarea:focus,
|
|
1223
|
+
.feedback-confirm__select:focus {
|
|
1224
|
+
outline: none;
|
|
1225
|
+
border-color: var(--feedback-accent, #4f46e5);
|
|
1226
|
+
box-shadow: 0 0 0 3px var(--feedback-accent-ring, rgba(79, 70, 229, 0.18));
|
|
1227
|
+
}
|
|
1228
|
+
.feedback-confirm__error {
|
|
1229
|
+
font-size: 12px;
|
|
1230
|
+
color: var(--feedback-danger, #dc2626);
|
|
1231
|
+
}
|
|
1232
|
+
.feedback-confirm__duplicate-warning {
|
|
1233
|
+
padding: 12px;
|
|
1234
|
+
font-size: 13px;
|
|
1235
|
+
background: var(--feedback-warning-bg, #fef3c7);
|
|
1236
|
+
border: 1px solid var(--feedback-warning-border, #fcd34d);
|
|
1237
|
+
border-radius: 8px;
|
|
1238
|
+
}
|
|
1239
|
+
.feedback-confirm__duplicate-link { color: var(--feedback-accent, #4f46e5); }
|
|
1240
|
+
.feedback-confirm__context-section {
|
|
1241
|
+
display: flex;
|
|
1242
|
+
flex-direction: column;
|
|
1243
|
+
gap: 8px;
|
|
1244
|
+
padding-top: 12px;
|
|
1245
|
+
border-top: 1px solid var(--feedback-border, #e5e7eb);
|
|
1246
|
+
font-size: 12px;
|
|
1247
|
+
color: var(--feedback-text-muted, #6b7280);
|
|
1248
|
+
word-break: break-word;
|
|
1249
|
+
}
|
|
1250
|
+
.feedback-confirm__screenshot-thumbnails {
|
|
1251
|
+
display: flex;
|
|
1252
|
+
flex-wrap: wrap;
|
|
1253
|
+
gap: 8px;
|
|
1254
|
+
}
|
|
1255
|
+
.feedback-confirm__screenshot-thumb {
|
|
1256
|
+
width: 72px;
|
|
1257
|
+
height: 72px;
|
|
1258
|
+
object-fit: cover;
|
|
1259
|
+
border: 1px solid var(--feedback-border, #d1d5db);
|
|
1260
|
+
border-radius: 6px;
|
|
1261
|
+
}
|
|
1262
|
+
.feedback-confirm__submit-error {
|
|
1263
|
+
display: flex;
|
|
1264
|
+
align-items: center;
|
|
1265
|
+
gap: 12px;
|
|
1266
|
+
padding: 10px 12px;
|
|
1267
|
+
font-size: 13px;
|
|
1268
|
+
color: var(--feedback-danger, #dc2626);
|
|
1269
|
+
background: var(--feedback-danger-bg, #fef2f2);
|
|
1270
|
+
border: 1px solid var(--feedback-danger-border, #fecaca);
|
|
1271
|
+
border-radius: 8px;
|
|
1272
|
+
}
|
|
1273
|
+
.feedback-confirm__submit-btn,
|
|
1274
|
+
.feedback-panel__close-btn,
|
|
1275
|
+
.feedback-panel__discard-confirm-btn,
|
|
1276
|
+
.feedback-panel__discard-cancel-btn,
|
|
1277
|
+
.feedback-confirm__retry-btn {
|
|
1278
|
+
display: inline-flex;
|
|
1279
|
+
align-items: center;
|
|
1280
|
+
justify-content: center;
|
|
1281
|
+
gap: 8px;
|
|
1282
|
+
padding: 10px 18px;
|
|
1283
|
+
font-family: inherit;
|
|
1284
|
+
font-size: 14px;
|
|
1285
|
+
font-weight: 600;
|
|
1286
|
+
line-height: 1;
|
|
1287
|
+
cursor: pointer;
|
|
1288
|
+
border-radius: var(--feedback-input-radius, 8px);
|
|
1289
|
+
border: 1px solid transparent;
|
|
1290
|
+
background: var(--feedback-accent, #4f46e5);
|
|
1291
|
+
color: var(--feedback-accent-contrast, #ffffff);
|
|
1292
|
+
transition: background-color 0.15s ease, transform 0.1s ease;
|
|
1293
|
+
}
|
|
1294
|
+
.feedback-confirm__submit-btn { align-self: flex-end; }
|
|
1295
|
+
.feedback-confirm__submit-btn:hover,
|
|
1296
|
+
.feedback-panel__close-btn:hover,
|
|
1297
|
+
.feedback-panel__discard-confirm-btn:hover,
|
|
1298
|
+
.feedback-confirm__retry-btn:hover {
|
|
1299
|
+
background: var(--feedback-accent-hover, #4338ca);
|
|
1300
|
+
}
|
|
1301
|
+
.feedback-confirm__submit-btn:disabled {
|
|
1302
|
+
opacity: 0.6;
|
|
1303
|
+
cursor: not-allowed;
|
|
1304
|
+
}
|
|
1305
|
+
.feedback-confirm__retry-btn { padding: 6px 12px; }
|
|
1306
|
+
.feedback-panel__discard-cancel-btn {
|
|
1307
|
+
background: var(--feedback-input-bg, #ffffff);
|
|
1308
|
+
color: var(--feedback-text, #374151);
|
|
1309
|
+
border-color: var(--feedback-border, #d1d5db);
|
|
1310
|
+
}
|
|
1311
|
+
.feedback-panel__discard-cancel-btn:hover {
|
|
1312
|
+
background: var(--feedback-muted-bg, #f9fafb);
|
|
1313
|
+
}
|
|
1314
|
+
.feedback-confirm__spinner {
|
|
1315
|
+
width: 14px;
|
|
1316
|
+
height: 14px;
|
|
1317
|
+
border: 2px solid currentColor;
|
|
1318
|
+
border-top-color: transparent;
|
|
1319
|
+
border-radius: 50%;
|
|
1320
|
+
animation: feedback-spin 0.6s linear infinite;
|
|
1321
|
+
}
|
|
1322
|
+
@keyframes feedback-spin { to { transform: rotate(360deg); } }
|
|
1323
|
+
|
|
1324
|
+
/* ── Submitted view ─────────────────────────────────────────── */
|
|
1325
|
+
.feedback-panel__submitted {
|
|
1326
|
+
display: flex;
|
|
1327
|
+
flex-direction: column;
|
|
1328
|
+
align-items: center;
|
|
1329
|
+
gap: 12px;
|
|
1330
|
+
text-align: center;
|
|
1331
|
+
padding: 16px 8px;
|
|
1332
|
+
}
|
|
1333
|
+
.feedback-panel__submitted-icon {
|
|
1334
|
+
margin: 0;
|
|
1335
|
+
width: 48px;
|
|
1336
|
+
height: 48px;
|
|
1337
|
+
display: flex;
|
|
1338
|
+
align-items: center;
|
|
1339
|
+
justify-content: center;
|
|
1340
|
+
font-size: 24px;
|
|
1341
|
+
color: var(--feedback-success-contrast, #ffffff);
|
|
1342
|
+
background: var(--feedback-success, #16a34a);
|
|
1343
|
+
border-radius: 50%;
|
|
1344
|
+
}
|
|
1345
|
+
.feedback-panel__submitted-title { margin: 0; font-size: 18px; font-weight: 700; }
|
|
1346
|
+
.feedback-panel__submitted-id { margin: 0; font-size: 13px; color: var(--feedback-text-muted, #6b7280); }
|
|
1347
|
+
|
|
1348
|
+
/* ── Dialogue (conversational capture) ──────────────────────── */
|
|
1349
|
+
.feedback-dialogue { display: flex; flex-direction: column; gap: 12px; }
|
|
1350
|
+
.feedback-dialogue__bubble {
|
|
1351
|
+
max-width: 85%;
|
|
1352
|
+
padding: 10px 14px;
|
|
1353
|
+
font-size: 14px;
|
|
1354
|
+
line-height: 1.45;
|
|
1355
|
+
border-radius: 14px;
|
|
1356
|
+
}
|
|
1357
|
+
.feedback-dialogue__bubble--assistant {
|
|
1358
|
+
align-self: flex-start;
|
|
1359
|
+
background: var(--feedback-muted-bg, #f3f4f6);
|
|
1360
|
+
color: var(--feedback-text, #111827);
|
|
1361
|
+
border-bottom-left-radius: 4px;
|
|
1362
|
+
}
|
|
1363
|
+
.feedback-dialogue__bubble--user {
|
|
1364
|
+
align-self: flex-end;
|
|
1365
|
+
background: var(--feedback-accent, #4f46e5);
|
|
1366
|
+
color: var(--feedback-accent-contrast, #ffffff);
|
|
1367
|
+
border-bottom-right-radius: 4px;
|
|
1368
|
+
}
|
|
1369
|
+
.feedback-dialogue__bubble-label {
|
|
1370
|
+
font-size: 11px;
|
|
1371
|
+
font-weight: 600;
|
|
1372
|
+
text-transform: uppercase;
|
|
1373
|
+
letter-spacing: 0.04em;
|
|
1374
|
+
opacity: 0.7;
|
|
1375
|
+
}
|
|
1376
|
+
.feedback-dialogue__typing { display: inline-flex; gap: 4px; padding: 10px 14px; }
|
|
1377
|
+
|
|
1378
|
+
/* ── Recorder ───────────────────────────────────────────────── */
|
|
1379
|
+
.feedback-recorder { display: flex; flex-direction: column; gap: 12px; }
|
|
1380
|
+
`;
|
|
1381
|
+
let panelStylesInjected = false;
|
|
1382
|
+
function injectPanelStyles() {
|
|
1383
|
+
if (panelStylesInjected || typeof document === "undefined") return;
|
|
1384
|
+
const style = document.createElement("style");
|
|
1385
|
+
style.setAttribute("data-feedback-panel", "");
|
|
1386
|
+
style.textContent = PANEL_STYLES;
|
|
1387
|
+
document.head.appendChild(style);
|
|
1388
|
+
panelStylesInjected = true;
|
|
1389
|
+
}
|
|
1163
1390
|
function Portal({ children }) {
|
|
1164
1391
|
const [isClient, setIsClient] = useState(false);
|
|
1165
1392
|
useEffect(() => {
|
|
@@ -1256,6 +1483,9 @@ function FeedbackPanel() {
|
|
|
1256
1483
|
const [showDiscardConfirm, setShowDiscardConfirm] = useState(false);
|
|
1257
1484
|
const locale = ctx?.locale ?? "en";
|
|
1258
1485
|
const customTranslations = ctx?.config.i18n.customTranslations;
|
|
1486
|
+
useEffect(() => {
|
|
1487
|
+
injectPanelStyles();
|
|
1488
|
+
}, []);
|
|
1259
1489
|
useEffect(() => {
|
|
1260
1490
|
if (isOpen) {
|
|
1261
1491
|
triggerRef.current = document.activeElement;
|
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 {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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"}
|
|
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// Styles (CSS custom properties for theming)\n//\n// The floating button ships its own stylesheet (FeedbackButton). The panel\n// *content* (confirm form, dialogue, recorder, submitted + discard views) use\n// BEM class names but previously had NO CSS injected, so the inner UI rendered\n// unstyled (\"sloppy\": invisible inputs, no spacing). This stylesheet styles the\n// whole in-panel flow. Every value uses a --feedback-* custom property with a\n// sensible fallback so hosts can theme without needing this CSS.\n// ─────────────────────────────────────────────────────────────────\n\nconst PANEL_STYLES = `\n.feedback-panel__title {\n margin: 0 0 16px;\n padding-right: 32px;\n font-size: 18px;\n font-weight: 700;\n line-height: 1.3;\n color: var(--feedback-text, #111827);\n}\n.feedback-panel__close-button:hover {\n color: var(--feedback-text, #111827);\n opacity: 0.7;\n}\n\n/* ── Confirm form ───────────────────────────────────────────── */\n.feedback-confirm {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n.feedback-confirm__title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--feedback-text-muted, #6b7280);\n}\n.feedback-confirm__field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.feedback-confirm__label {\n font-size: 13px;\n font-weight: 600;\n color: var(--feedback-text, #374151);\n}\n.feedback-confirm__required {\n color: var(--feedback-danger, #dc2626);\n}\n.feedback-confirm__input,\n.feedback-confirm__textarea,\n.feedback-confirm__select {\n width: 100%;\n box-sizing: border-box;\n padding: 9px 12px;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.4;\n color: var(--feedback-text, #111827);\n background: var(--feedback-input-bg, #ffffff);\n border: 1px solid var(--feedback-border, #d1d5db);\n border-radius: var(--feedback-input-radius, 8px);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.feedback-confirm__textarea {\n min-height: 88px;\n resize: vertical;\n}\n.feedback-confirm__input:focus,\n.feedback-confirm__textarea:focus,\n.feedback-confirm__select:focus {\n outline: none;\n border-color: var(--feedback-accent, #4f46e5);\n box-shadow: 0 0 0 3px var(--feedback-accent-ring, rgba(79, 70, 229, 0.18));\n}\n.feedback-confirm__error {\n font-size: 12px;\n color: var(--feedback-danger, #dc2626);\n}\n.feedback-confirm__duplicate-warning {\n padding: 12px;\n font-size: 13px;\n background: var(--feedback-warning-bg, #fef3c7);\n border: 1px solid var(--feedback-warning-border, #fcd34d);\n border-radius: 8px;\n}\n.feedback-confirm__duplicate-link { color: var(--feedback-accent, #4f46e5); }\n.feedback-confirm__context-section {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding-top: 12px;\n border-top: 1px solid var(--feedback-border, #e5e7eb);\n font-size: 12px;\n color: var(--feedback-text-muted, #6b7280);\n word-break: break-word;\n}\n.feedback-confirm__screenshot-thumbnails {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.feedback-confirm__screenshot-thumb {\n width: 72px;\n height: 72px;\n object-fit: cover;\n border: 1px solid var(--feedback-border, #d1d5db);\n border-radius: 6px;\n}\n.feedback-confirm__submit-error {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n font-size: 13px;\n color: var(--feedback-danger, #dc2626);\n background: var(--feedback-danger-bg, #fef2f2);\n border: 1px solid var(--feedback-danger-border, #fecaca);\n border-radius: 8px;\n}\n.feedback-confirm__submit-btn,\n.feedback-panel__close-btn,\n.feedback-panel__discard-confirm-btn,\n.feedback-panel__discard-cancel-btn,\n.feedback-confirm__retry-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 10px 18px;\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n border-radius: var(--feedback-input-radius, 8px);\n border: 1px solid transparent;\n background: var(--feedback-accent, #4f46e5);\n color: var(--feedback-accent-contrast, #ffffff);\n transition: background-color 0.15s ease, transform 0.1s ease;\n}\n.feedback-confirm__submit-btn { align-self: flex-end; }\n.feedback-confirm__submit-btn:hover,\n.feedback-panel__close-btn:hover,\n.feedback-panel__discard-confirm-btn:hover,\n.feedback-confirm__retry-btn:hover {\n background: var(--feedback-accent-hover, #4338ca);\n}\n.feedback-confirm__submit-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n.feedback-confirm__retry-btn { padding: 6px 12px; }\n.feedback-panel__discard-cancel-btn {\n background: var(--feedback-input-bg, #ffffff);\n color: var(--feedback-text, #374151);\n border-color: var(--feedback-border, #d1d5db);\n}\n.feedback-panel__discard-cancel-btn:hover {\n background: var(--feedback-muted-bg, #f9fafb);\n}\n.feedback-confirm__spinner {\n width: 14px;\n height: 14px;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n animation: feedback-spin 0.6s linear infinite;\n}\n@keyframes feedback-spin { to { transform: rotate(360deg); } }\n\n/* ── Submitted view ─────────────────────────────────────────── */\n.feedback-panel__submitted {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n text-align: center;\n padding: 16px 8px;\n}\n.feedback-panel__submitted-icon {\n margin: 0;\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 24px;\n color: var(--feedback-success-contrast, #ffffff);\n background: var(--feedback-success, #16a34a);\n border-radius: 50%;\n}\n.feedback-panel__submitted-title { margin: 0; font-size: 18px; font-weight: 700; }\n.feedback-panel__submitted-id { margin: 0; font-size: 13px; color: var(--feedback-text-muted, #6b7280); }\n\n/* ── Dialogue (conversational capture) ──────────────────────── */\n.feedback-dialogue { display: flex; flex-direction: column; gap: 12px; }\n.feedback-dialogue__bubble {\n max-width: 85%;\n padding: 10px 14px;\n font-size: 14px;\n line-height: 1.45;\n border-radius: 14px;\n}\n.feedback-dialogue__bubble--assistant {\n align-self: flex-start;\n background: var(--feedback-muted-bg, #f3f4f6);\n color: var(--feedback-text, #111827);\n border-bottom-left-radius: 4px;\n}\n.feedback-dialogue__bubble--user {\n align-self: flex-end;\n background: var(--feedback-accent, #4f46e5);\n color: var(--feedback-accent-contrast, #ffffff);\n border-bottom-right-radius: 4px;\n}\n.feedback-dialogue__bubble-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n opacity: 0.7;\n}\n.feedback-dialogue__typing { display: inline-flex; gap: 4px; padding: 10px 14px; }\n\n/* ── Recorder ───────────────────────────────────────────────── */\n.feedback-recorder { display: flex; flex-direction: column; gap: 12px; }\n`;\n\nlet panelStylesInjected = false;\nfunction injectPanelStyles(): void {\n if (panelStylesInjected || typeof document === 'undefined') return;\n const style = document.createElement('style');\n style.setAttribute('data-feedback-panel', '');\n style.textContent = PANEL_STYLES;\n document.head.appendChild(style);\n panelStylesInjected = true;\n}\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 // Inject the in-panel stylesheet once (styles the confirm form, dialogue,\n // recorder, submitted + discard views — the button ships its own CSS).\n useEffect(() => {\n injectPanelStyles();\n }, []);\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