@mhosaic/feedback 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -50,7 +50,10 @@ function createApiClient(options) {
50
50
  form.append(field, String(payload[field]));
51
51
  }
52
52
  form.append("technical_context", JSON.stringify(payload.technical_context));
53
- if (payload.screenshot) form.append("screenshot", payload.screenshot, "screenshot.png");
53
+ const screenshots = payload.screenshots?.length ? payload.screenshots : payload.screenshot ? [payload.screenshot] : [];
54
+ screenshots.forEach((blob, i) => {
55
+ form.append("screenshot", blob, i === 0 ? "screenshot.png" : `screenshot-${i + 1}.png`);
56
+ });
54
57
  if (payload.synthetic) form.append("synthetic", "true");
55
58
  if (payload.user?.id) {
56
59
  form.append("user", JSON.stringify(payload.user));
@@ -58,6 +61,9 @@ function createApiClient(options) {
58
61
  if (payload.widget_version) {
59
62
  form.append("widget_version", payload.widget_version);
60
63
  }
64
+ if (payload.page_path) {
65
+ form.append("page_path", payload.page_path);
66
+ }
61
67
  const headers = {
62
68
  Authorization: `Bearer ${options.apiKey}`
63
69
  };
@@ -197,6 +203,7 @@ function createApiClient(options) {
197
203
  if (filters.mine) params.set("mine", "1");
198
204
  if (filters.ordering) params.set("ordering", filters.ordering);
199
205
  if (filters.page && filters.page > 1) params.set("page", String(filters.page));
206
+ if (filters.pagePath) params.set("page_path", filters.pagePath);
200
207
  const qs = params.toString();
201
208
  return qs ? `?${qs}` : "";
202
209
  }
@@ -323,7 +330,7 @@ function installConsolePatch(buffer) {
323
330
  const original = console[level];
324
331
  if (typeof original !== "function") continue;
325
332
  originals[level] = original;
326
- console[level] = function patched(...args) {
333
+ console[level] = function patched2(...args) {
327
334
  try {
328
335
  const rawMessage = args.map(safeStringify).join(" ");
329
336
  const message = scrubCredentials(rawMessage).slice(0, 2e3);
@@ -350,7 +357,7 @@ function installFetchPatch(buffer, sanitize) {
350
357
  if (typeof window === "undefined" || typeof window.fetch !== "function") return () => {
351
358
  };
352
359
  const original = window.fetch.bind(window);
353
- window.fetch = async function patched(input, init) {
360
+ window.fetch = async function patched2(input, init) {
354
361
  const start = performance.now();
355
362
  const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
356
363
  const method = (init?.method || (input instanceof Request ? input.method : "GET")).toUpperCase();
@@ -568,6 +575,7 @@ var DEFAULT_STRINGS = {
568
575
  "form.screenshot.annotate": "Annotate",
569
576
  "form.screenshot.error_type": "Unsupported file type. Use PNG, JPEG or WebP.",
570
577
  "form.screenshot.error_size": "File too large (max {max} MB).",
578
+ "form.screenshot.error_count": "Too many screenshots (max {max}).",
571
579
  "form.context.label": "Page",
572
580
  "type.bug": "Bug",
573
581
  "type.feature": "Feature request",
@@ -630,6 +638,7 @@ var DEFAULT_STRINGS = {
630
638
  "board.detail.status_history": "History",
631
639
  "board.scope.project": "Project reports",
632
640
  "board.scope.mine": "Your reports",
641
+ "board.scope.thisPage": "This page",
633
642
  "board.back": "Back",
634
643
  "changelog.empty.title": "Nothing resolved yet",
635
644
  "changelog.empty.body": "Once a report you sent is fixed it will appear here, grouped by week.",
@@ -717,6 +726,7 @@ var FRENCH_STRINGS = {
717
726
  "form.screenshot.annotate": "Annoter",
718
727
  "form.screenshot.error_type": "Format non support\xE9. Utilisez PNG, JPEG ou WebP.",
719
728
  "form.screenshot.error_size": "Fichier trop volumineux (max {max} Mo).",
729
+ "form.screenshot.error_count": "Trop de captures d\u2019\xE9cran (max {max}).",
720
730
  "form.context.label": "Page",
721
731
  "type.bug": "Bogue",
722
732
  "type.feature": "Suggestion",
@@ -779,6 +789,7 @@ var FRENCH_STRINGS = {
779
789
  "board.detail.status_history": "Historique",
780
790
  "board.scope.project": "Rapports du projet",
781
791
  "board.scope.mine": "Vos rapports",
792
+ "board.scope.thisPage": "Cette page",
782
793
  "board.back": "Retour",
783
794
  "changelog.empty.title": "Rien de r\xE9solu pour l\u2019instant",
784
795
  "changelog.empty.body": "Quand un rapport que vous avez envoy\xE9 est corrig\xE9, il appara\xEEtra ici, regroup\xE9 par semaine.",
@@ -865,6 +876,36 @@ function resolveStrings(overrides, options = {}) {
865
876
  import { h, render } from "preact";
866
877
  import { useCallback, useEffect as useEffect8, useState as useState7 } from "preact/hooks";
867
878
 
879
+ // src/widget/currentPage.ts
880
+ function currentPagePath(opts) {
881
+ const override = opts.getCurrentPage?.();
882
+ if (override) return override;
883
+ return typeof window !== "undefined" ? window.location.pathname : "/";
884
+ }
885
+ var LOCATION_CHANGE = "mfb:locationchange";
886
+ var patched = false;
887
+ function patchHistory() {
888
+ if (patched || typeof history === "undefined") return;
889
+ patched = true;
890
+ for (const m of ["pushState", "replaceState"]) {
891
+ const orig = history[m];
892
+ history[m] = function(...args) {
893
+ const r = orig.apply(this, args);
894
+ window.dispatchEvent(new Event(LOCATION_CHANGE));
895
+ return r;
896
+ };
897
+ }
898
+ }
899
+ function onLocationChange(cb) {
900
+ patchHistory();
901
+ window.addEventListener("popstate", cb);
902
+ window.addEventListener(LOCATION_CHANGE, cb);
903
+ return () => {
904
+ window.removeEventListener("popstate", cb);
905
+ window.removeEventListener(LOCATION_CHANGE, cb);
906
+ };
907
+ }
908
+
868
909
  // src/widget/BoardView.tsx
869
910
  import { useEffect as useEffect2, useMemo, useState as useState2 } from "preact/hooks";
870
911
 
@@ -923,6 +964,7 @@ var ALLOWED_IMAGE_TYPES = [
923
964
  "image/webp"
924
965
  ];
925
966
  var MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
967
+ var MAX_SCREENSHOTS = 5;
926
968
  function validateScreenshotFile(file) {
927
969
  if (!ALLOWED_IMAGE_TYPES.includes(
928
970
  file.type
@@ -1092,8 +1134,8 @@ function ReportDetailView({
1092
1134
  /* @__PURE__ */ jsxs2("div", { class: "report-detail-body", children: [
1093
1135
  /* @__PURE__ */ jsx2(ContextBlock, { detail, strings }),
1094
1136
  /* @__PURE__ */ jsx2("p", { class: "report-detail-description", children: detail.description }),
1095
- detail.screenshot_url && (() => {
1096
- const safeHref = safeExternalHref(detail.screenshot_url);
1137
+ (detail.screenshots?.length ? detail.screenshots.map((s) => s.url) : [detail.screenshot_url]).filter((url) => Boolean(url)).map((url) => {
1138
+ const safeHref = safeExternalHref(url);
1097
1139
  return safeHref ? /* @__PURE__ */ jsx2(
1098
1140
  "a",
1099
1141
  {
@@ -1101,10 +1143,10 @@ function ReportDetailView({
1101
1143
  href: safeHref,
1102
1144
  target: "_blank",
1103
1145
  rel: "noopener noreferrer",
1104
- children: /* @__PURE__ */ jsx2("img", { src: detail.screenshot_url, alt: "", loading: "lazy" })
1146
+ children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" })
1105
1147
  }
1106
- ) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: detail.screenshot_url, alt: "", loading: "lazy" }) });
1107
- })(),
1148
+ ) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" }) });
1149
+ }),
1108
1150
  /* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
1109
1151
  detail.comments.length === 0 ? /* @__PURE__ */ jsx2("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx2("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(CommentBubble, { comment: c, strings }) })) }),
1110
1152
  detail.status_history && detail.status_history.length > 0 && /* @__PURE__ */ jsx2(StatusHistorySection, { rows: detail.status_history, strings }),
@@ -1335,8 +1377,9 @@ var STATUSES = [
1335
1377
  ];
1336
1378
  var TYPES = ["bug", "feature", "question", "praise", "typo"];
1337
1379
  var SEVERITIES = ["blocker", "high", "medium", "low"];
1338
- function BoardView({ api, externalId, strings }) {
1380
+ function BoardView({ api, externalId, strings, getCurrentPage }) {
1339
1381
  const [filters, setFilters] = useState2(() => loadBoardView(externalId));
1382
+ const [thisPage, setThisPage] = useState2(true);
1340
1383
  useEffect2(() => {
1341
1384
  saveBoardView(externalId, filters);
1342
1385
  }, [externalId, filtersHash(filters)]);
@@ -1350,14 +1393,23 @@ function BoardView({ api, externalId, strings }) {
1350
1393
  const activeFilterCount = useMemo(() => {
1351
1394
  return (filters.status?.length ?? 0) + (filters.type?.length ?? 0) + (filters.severity?.length ?? 0) + (filters.q ? 1 : 0) + (filters.mine ? 1 : 0);
1352
1395
  }, [filters]);
1396
+ useEffect2(() => {
1397
+ if (!thisPage) return;
1398
+ return onLocationChange(() => setReloadTick((t) => t + 1));
1399
+ }, [thisPage]);
1400
+ const fetchFilters = useMemo(
1401
+ () => thisPage ? { ...filters, pagePath: currentPagePath(getCurrentPage !== void 0 ? { getCurrentPage } : {}) } : filters,
1402
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1403
+ [filtersHash(filters), thisPage, reloadTick]
1404
+ );
1353
1405
  useEffect2(() => {
1354
1406
  let cancelled = false;
1355
1407
  let timer = null;
1356
1408
  const tick = async () => {
1357
1409
  try {
1358
1410
  const [list, k] = await Promise.all([
1359
- api.listBoard(externalId, filters),
1360
- api.fetchBoardKpis(externalId, filters)
1411
+ api.listBoard(externalId, fetchFilters),
1412
+ api.fetchBoardKpis(externalId, fetchFilters)
1361
1413
  ]);
1362
1414
  if (cancelled) return;
1363
1415
  setPage(list);
@@ -1377,7 +1429,7 @@ function BoardView({ api, externalId, strings }) {
1377
1429
  cancelled = true;
1378
1430
  if (timer) clearTimeout(timer);
1379
1431
  };
1380
- }, [api, externalId, filtersHash(filters), reloadTick]);
1432
+ }, [api, externalId, JSON.stringify(fetchFilters)]);
1381
1433
  const selectedRow = useMemo(() => {
1382
1434
  if (!selectedId || !page) return null;
1383
1435
  return page.results.find((r) => r.id === selectedId) ?? null;
@@ -1394,7 +1446,9 @@ function BoardView({ api, externalId, strings }) {
1394
1446
  filters,
1395
1447
  onChange: setFilters,
1396
1448
  activeCount: activeFilterCount,
1397
- strings
1449
+ strings,
1450
+ thisPage,
1451
+ onToggleThisPage: () => setThisPage((v) => !v)
1398
1452
  }
1399
1453
  ),
1400
1454
  /* @__PURE__ */ jsxs3("div", { class: "board-body", children: [
@@ -1502,7 +1556,9 @@ function BoardFilters({
1502
1556
  filters,
1503
1557
  onChange,
1504
1558
  activeCount,
1505
- strings
1559
+ strings,
1560
+ thisPage,
1561
+ onToggleThisPage
1506
1562
  }) {
1507
1563
  const setStatus = (value) => {
1508
1564
  if (value === "") {
@@ -1614,6 +1670,19 @@ function BoardFilters({
1614
1670
  ),
1615
1671
  /* @__PURE__ */ jsx3("span", { children: strings["board.filter.mine"] })
1616
1672
  ] }),
1673
+ /* @__PURE__ */ jsxs3(
1674
+ "button",
1675
+ {
1676
+ type: "button",
1677
+ class: `board-chip ${thisPage ? "board-chip--on" : ""}`,
1678
+ onClick: onToggleThisPage,
1679
+ children: [
1680
+ "\u{1F4CD} ",
1681
+ strings["board.scope.thisPage"],
1682
+ thisPage ? " \u2715" : ""
1683
+ ]
1684
+ }
1685
+ ),
1617
1686
  activeCount > 0 && /* @__PURE__ */ jsxs3("button", { type: "button", class: "board-filter-clear", onClick: clear, children: [
1618
1687
  /* @__PURE__ */ jsx3(CloseIcon, {}),
1619
1688
  strings["board.filter.clear"]
@@ -2456,45 +2525,58 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2456
2525
  const [feedbackType, setFeedbackType] = useState5("bug");
2457
2526
  const [severity, setSeverity] = useState5("medium");
2458
2527
  const [localError, setLocalError] = useState5("");
2459
- const [screenshotBlob, setScreenshotBlob] = useState5(null);
2460
- const [screenshotPreview, setScreenshotPreview] = useState5(null);
2528
+ const [shots, setShots] = useState5([]);
2461
2529
  const [isDragOver, setIsDragOver] = useState5(false);
2462
- const [annotatorOpen, setAnnotatorOpen] = useState5(false);
2530
+ const [annotatingIndex, setAnnotatingIndex] = useState5(null);
2463
2531
  const fileInputRef = useRef4(null);
2464
2532
  const dropZoneRef = useRef4(null);
2465
2533
  const submitting = status === "submitting";
2466
2534
  const submitLabel = submitting ? strings["form.submitting"] : strings["form.submit"];
2467
2535
  const pageUrl = typeof window !== "undefined" ? window.location.href : "";
2536
+ const shotsRef = useRef4(shots);
2537
+ shotsRef.current = shots;
2468
2538
  useEffect5(() => {
2469
2539
  return () => {
2470
- if (screenshotPreview) URL.revokeObjectURL(screenshotPreview);
2540
+ shotsRef.current.forEach((s) => URL.revokeObjectURL(s.preview));
2471
2541
  };
2472
- }, [screenshotPreview]);
2473
- const acceptFile = (file) => {
2542
+ }, []);
2543
+ const acceptFiles = (files) => {
2474
2544
  setLocalError("");
2475
- if (file instanceof File) {
2476
- const err = validateScreenshotFile(file);
2477
- if (err) {
2478
- setLocalError(
2479
- err.kind === "type" ? strings["form.screenshot.error_type"] : strings["form.screenshot.error_size"].replace("{max}", String(err.maxMb))
2480
- );
2481
- return;
2545
+ const remaining = MAX_SCREENSHOTS - shots.length;
2546
+ const batch = files.slice(0, Math.max(0, remaining));
2547
+ for (const file of batch) {
2548
+ if (file instanceof File) {
2549
+ const err = validateScreenshotFile(file);
2550
+ if (err) {
2551
+ setLocalError(
2552
+ err.kind === "type" ? strings["form.screenshot.error_type"] : strings["form.screenshot.error_size"].replace("{max}", String(err.maxMb))
2553
+ );
2554
+ return;
2555
+ }
2482
2556
  }
2483
2557
  }
2484
- if (screenshotPreview) URL.revokeObjectURL(screenshotPreview);
2485
- setScreenshotBlob(file);
2486
- setScreenshotPreview(URL.createObjectURL(file));
2558
+ if (batch.length) {
2559
+ const incoming = batch.map((blob) => ({ blob, preview: URL.createObjectURL(blob) }));
2560
+ setShots((prev) => [...prev, ...incoming]);
2561
+ }
2562
+ if (files.length > batch.length) {
2563
+ setLocalError(
2564
+ strings["form.screenshot.error_count"].replace("{max}", String(MAX_SCREENSHOTS))
2565
+ );
2566
+ }
2487
2567
  };
2488
- const clearScreenshot = () => {
2489
- if (screenshotPreview) URL.revokeObjectURL(screenshotPreview);
2490
- setScreenshotPreview(null);
2491
- setScreenshotBlob(null);
2568
+ const removeShot = (index) => {
2569
+ const shot = shots[index];
2570
+ if (shot) URL.revokeObjectURL(shot.preview);
2571
+ setShots((prev) => prev.filter((_, i) => i !== index));
2492
2572
  setLocalError("");
2493
2573
  if (fileInputRef.current) fileInputRef.current.value = "";
2494
2574
  };
2495
2575
  const handleFileInputChange = (e) => {
2496
- const file = e.target.files?.[0];
2497
- if (file) acceptFile(file);
2576
+ const input = e.target;
2577
+ const files = Array.from(input.files ?? []);
2578
+ if (files.length) acceptFiles(files);
2579
+ input.value = "";
2498
2580
  };
2499
2581
  const handleDragOver = (e) => {
2500
2582
  e.preventDefault();
@@ -2510,8 +2592,8 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2510
2592
  e.preventDefault();
2511
2593
  e.stopPropagation();
2512
2594
  setIsDragOver(false);
2513
- const file = e.dataTransfer?.files?.[0];
2514
- if (file) acceptFile(file);
2595
+ const files = Array.from(e.dataTransfer?.files ?? []);
2596
+ if (files.length) acceptFiles(files);
2515
2597
  };
2516
2598
  useEffect5(() => {
2517
2599
  const zone = dropZoneRef.current;
@@ -2524,7 +2606,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2524
2606
  const file = item.getAsFile();
2525
2607
  if (file) {
2526
2608
  e.preventDefault();
2527
- acceptFile(file);
2609
+ acceptFiles([file]);
2528
2610
  return;
2529
2611
  }
2530
2612
  }
@@ -2532,12 +2614,17 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2532
2614
  };
2533
2615
  zone.addEventListener("paste", onPaste);
2534
2616
  return () => zone.removeEventListener("paste", onPaste);
2535
- }, [screenshotPreview]);
2617
+ }, [shots.length]);
2536
2618
  const handleAnnotated = (annotated) => {
2537
- if (screenshotPreview) URL.revokeObjectURL(screenshotPreview);
2538
- setScreenshotBlob(annotated);
2539
- setScreenshotPreview(URL.createObjectURL(annotated));
2540
- setAnnotatorOpen(false);
2619
+ const index = annotatingIndex;
2620
+ if (index === null || !shots[index]) {
2621
+ setAnnotatingIndex(null);
2622
+ return;
2623
+ }
2624
+ URL.revokeObjectURL(shots[index].preview);
2625
+ const replacement = { blob: annotated, preview: URL.createObjectURL(annotated) };
2626
+ setShots((prev) => prev.map((s, i) => i === index ? replacement : s));
2627
+ setAnnotatingIndex(null);
2541
2628
  };
2542
2629
  const handleSubmit = (e) => {
2543
2630
  e.preventDefault();
@@ -2551,8 +2638,8 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2551
2638
  feedback_type: feedbackType,
2552
2639
  severity
2553
2640
  };
2554
- if (screenshotBlob) {
2555
- values.screenshot = screenshotBlob;
2641
+ if (shots.length) {
2642
+ values.screenshots = shots.map((s) => s.blob);
2556
2643
  values.capture_method = "manual";
2557
2644
  }
2558
2645
  onSubmit(values);
@@ -2605,21 +2692,22 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2605
2692
  ref: fileInputRef,
2606
2693
  type: "file",
2607
2694
  accept: "image/png,image/jpeg,image/webp",
2695
+ multiple: true,
2608
2696
  class: "mfb-sr-only",
2609
2697
  onChange: handleFileInputChange,
2610
2698
  "aria-hidden": "true",
2611
2699
  tabIndex: -1
2612
2700
  }
2613
2701
  ),
2614
- screenshotPreview ? /* @__PURE__ */ jsxs8("div", { class: "screenshot-preview", children: [
2615
- /* @__PURE__ */ jsx8("img", { src: screenshotPreview, alt: "" }),
2702
+ shots.length > 0 && /* @__PURE__ */ jsx8("div", { class: "screenshot-strip", children: shots.map((shot, index) => /* @__PURE__ */ jsxs8("div", { class: "screenshot-preview", children: [
2703
+ /* @__PURE__ */ jsx8("img", { src: shot.preview, alt: "" }),
2616
2704
  /* @__PURE__ */ jsxs8("div", { class: "screenshot-preview-actions", children: [
2617
2705
  /* @__PURE__ */ jsxs8(
2618
2706
  "button",
2619
2707
  {
2620
2708
  type: "button",
2621
2709
  class: "btn btn--primary screenshot-annotate",
2622
- onClick: () => setAnnotatorOpen(true),
2710
+ onClick: () => setAnnotatingIndex(index),
2623
2711
  children: [
2624
2712
  /* @__PURE__ */ jsxs8("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
2625
2713
  /* @__PURE__ */ jsx8("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
@@ -2629,12 +2717,13 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2629
2717
  ]
2630
2718
  }
2631
2719
  ),
2632
- /* @__PURE__ */ jsxs8("button", { type: "button", class: "btn", onClick: clearScreenshot, children: [
2720
+ /* @__PURE__ */ jsxs8("button", { type: "button", class: "btn", onClick: () => removeShot(index), children: [
2633
2721
  /* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" }) }),
2634
2722
  strings["form.screenshot.remove"]
2635
2723
  ] })
2636
2724
  ] })
2637
- ] }) : /* @__PURE__ */ jsxs8(
2725
+ ] }, shot.preview)) }),
2726
+ shots.length < MAX_SCREENSHOTS && /* @__PURE__ */ jsxs8(
2638
2727
  "div",
2639
2728
  {
2640
2729
  ref: dropZoneRef,
@@ -2679,12 +2768,12 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2679
2768
  /* @__PURE__ */ jsx8("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
2680
2769
  /* @__PURE__ */ jsx8("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
2681
2770
  ] }),
2682
- annotatorOpen && screenshotBlob && /* @__PURE__ */ jsx8(
2771
+ annotatingIndex !== null && shots[annotatingIndex] && /* @__PURE__ */ jsx8(
2683
2772
  Annotator,
2684
2773
  {
2685
- imageBlob: screenshotBlob,
2774
+ imageBlob: shots[annotatingIndex].blob,
2686
2775
  strings,
2687
- onCancel: () => setAnnotatorOpen(false),
2776
+ onCancel: () => setAnnotatingIndex(null),
2688
2777
  onSave: handleAnnotated
2689
2778
  }
2690
2779
  )
@@ -3319,6 +3408,12 @@ var WIDGET_STYLES = `
3319
3408
  .screenshot-cta strong { color: var(--mfb-accent); font-weight: 600; }
3320
3409
  .screenshot-formats { font-size: var(--mfb-text-xs); color: var(--mfb-text-muted); }
3321
3410
 
3411
+ .screenshot-strip {
3412
+ display: flex;
3413
+ flex-direction: column;
3414
+ gap: var(--mfb-space-2);
3415
+ margin-bottom: var(--mfb-space-2);
3416
+ }
3322
3417
  .screenshot-preview {
3323
3418
  position: relative;
3324
3419
  border: 1px solid var(--mfb-border);
@@ -3336,6 +3431,10 @@ var WIDGET_STYLES = `
3336
3431
  object-fit: contain;
3337
3432
  background: #1a1a1a;
3338
3433
  }
3434
+ /* In the multi-shot strip each preview stays compact so several fit. */
3435
+ .screenshot-strip .screenshot-preview img {
3436
+ max-height: 120px;
3437
+ }
3339
3438
  .screenshot-preview-actions {
3340
3439
  display: flex;
3341
3440
  gap: var(--mfb-space-2);
@@ -4626,6 +4725,17 @@ function mountWidget(options) {
4626
4725
  void _drop;
4627
4726
  return rest;
4628
4727
  }
4728
+ function openWidget(externalId) {
4729
+ rerender({ ...currentState, open: true, tab: "send" });
4730
+ if (options.openToCurrentPageFeedback && options.api && externalId) {
4731
+ options.api.fetchBoardKpis(externalId, { pagePath: currentPagePath(options) }).then((kpis) => {
4732
+ if (kpis.total > 0 && currentState.open && currentState.tab === "send") {
4733
+ rerender({ ...currentState, tab: "board" });
4734
+ }
4735
+ }).catch(() => {
4736
+ });
4737
+ }
4738
+ }
4629
4739
  function Root({ state }) {
4630
4740
  const handleSubmit = useCallback(async (values) => {
4631
4741
  rerender({ ...currentState, status: "submitting" });
@@ -4685,7 +4795,7 @@ function mountWidget(options) {
4685
4795
  Fab,
4686
4796
  {
4687
4797
  label: options.strings["fab.label"],
4688
- onClick: () => rerender({ ...currentState, open: true })
4798
+ onClick: () => openWidget(externalId)
4689
4799
  }
4690
4800
  ),
4691
4801
  state.open && /* @__PURE__ */ jsxs12(
@@ -4786,7 +4896,8 @@ function mountWidget(options) {
4786
4896
  {
4787
4897
  api: options.api,
4788
4898
  externalId,
4789
- strings: options.strings
4899
+ strings: options.strings,
4900
+ ...options.getCurrentPage !== void 0 && { getCurrentPage: options.getCurrentPage }
4790
4901
  }
4791
4902
  )
4792
4903
  ]
@@ -4797,7 +4908,7 @@ function mountWidget(options) {
4797
4908
  rerender(currentState);
4798
4909
  return {
4799
4910
  open() {
4800
- rerender({ ...currentState, open: true });
4911
+ openWidget(options.getExternalId?.());
4801
4912
  },
4802
4913
  close() {
4803
4914
  rerender({ ...currentState, open: false, status: "idle" });
@@ -4860,7 +4971,8 @@ function createFeedback(config) {
4860
4971
  document.body.appendChild(host);
4861
4972
  }
4862
4973
  async function buildAndSubmit(values) {
4863
- const manualScreenshot = values.synthetic ? void 0 : values.screenshot;
4974
+ const attached = values.screenshots?.length ? values.screenshots : values.screenshot ? [values.screenshot] : void 0;
4975
+ const manualScreenshots = values.synthetic ? void 0 : attached;
4864
4976
  const technical_context = capture.snapshot();
4865
4977
  if (user) {
4866
4978
  const { userHash: _hash, exp: _exp, ...safeUser } = user;
@@ -4869,7 +4981,7 @@ function createFeedback(config) {
4869
4981
  if (metadata && Object.keys(metadata).length > 0) {
4870
4982
  technical_context.metadata = { ...metadata };
4871
4983
  }
4872
- const captureMethod = manualScreenshot ? values.capture_method ?? "manual" : "none";
4984
+ const captureMethod = manualScreenshots?.length ? values.capture_method ?? "manual" : "none";
4873
4985
  const payload = {
4874
4986
  description: values.description,
4875
4987
  feedback_type: values.feedback_type ?? "bug",
@@ -4880,11 +4992,16 @@ function createFeedback(config) {
4880
4992
  capture_method: captureMethod,
4881
4993
  technical_context
4882
4994
  };
4883
- if ("0.22.0") {
4884
- payload.widget_version = "0.22.0";
4995
+ if ("0.24.0") {
4996
+ payload.widget_version = "0.24.0";
4997
+ }
4998
+ if (manualScreenshots?.length) {
4999
+ payload.screenshots = manualScreenshots;
5000
+ payload.screenshot = manualScreenshots[0];
4885
5001
  }
4886
- if (manualScreenshot) payload.screenshot = manualScreenshot;
4887
5002
  if (values.synthetic) payload.synthetic = true;
5003
+ const pagePath = currentPagePath(config);
5004
+ if (pagePath) payload.page_path = pagePath;
4888
5005
  if (user?.id !== void 0 && user.id !== null && user.id !== "") {
4889
5006
  payload.user = {
4890
5007
  // The host can pass `id` as a string or number; the backend
@@ -4925,7 +5042,14 @@ function createFeedback(config) {
4925
5042
  // Send the identified email alongside the id so an email-based allowlist
4926
5043
  // can match (the backend matches external_id OR email). Read from `user`
4927
5044
  // live so identity set after createFeedback() is reflected.
4928
- checkVisibility: (externalId) => api.checkVisibility(externalId, user?.email)
5045
+ checkVisibility: (externalId) => api.checkVisibility(externalId, user?.email),
5046
+ // Thread the host's getCurrentPage override so the Board chip resolves
5047
+ // the page identically to what the submit side records (§ currentPage).
5048
+ ...config.getCurrentPage !== void 0 && { getCurrentPage: config.getCurrentPage },
5049
+ // Opt-in: FAB opens to the page-scoped Board (default off → Send-first).
5050
+ ...config.openToCurrentPageFeedback !== void 0 && {
5051
+ openToCurrentPageFeedback: config.openToCurrentPageFeedback
5052
+ }
4929
5053
  });
4930
5054
  let qaHandle;
4931
5055
  let qaDisposed = false;
@@ -4960,7 +5084,8 @@ function createFeedback(config) {
4960
5084
  ...partial.feedback_type !== void 0 && { feedback_type: partial.feedback_type },
4961
5085
  ...partial.severity !== void 0 && { severity: partial.severity },
4962
5086
  ...partial.synthetic !== void 0 && { synthetic: partial.synthetic },
4963
- ...partial.screenshot !== void 0 && { screenshot: partial.screenshot }
5087
+ ...partial.screenshot !== void 0 && { screenshot: partial.screenshot },
5088
+ ...partial.screenshots !== void 0 && { screenshots: partial.screenshots }
4964
5089
  });
4965
5090
  },
4966
5091
  identify(u) {
@@ -4992,4 +5117,4 @@ function createFeedback(config) {
4992
5117
  export {
4993
5118
  createFeedback
4994
5119
  };
4995
- //# sourceMappingURL=chunk-6OAG72JW.mjs.map
5120
+ //# sourceMappingURL=chunk-WIQ7MTPI.mjs.map