@mhosaic/feedback 0.35.0 → 0.37.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.
@@ -20,9 +20,7 @@ function assertSafeEndpoint(endpoint) {
20
20
  try {
21
21
  parsed = new URL(endpoint);
22
22
  } catch {
23
- throw new Error(
24
- `[mhosaic-feedback] \`endpoint\` is not a valid URL: ${endpoint}`
25
- );
23
+ throw new Error(`[mhosaic-feedback] \`endpoint\` is not a valid URL: ${endpoint}`);
26
24
  }
27
25
  if (parsed.protocol === "https:") return;
28
26
  if (parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]")) {
@@ -91,6 +89,7 @@ function createApiClient(options) {
91
89
  form.append("screenshot", blob, i === 0 ? "screenshot.png" : `screenshot-${i + 1}.png`);
92
90
  });
93
91
  if (payload.synthetic) form.append("synthetic", "true");
92
+ if (payload.meta) form.append("meta", "true");
94
93
  if (payload.user?.id) {
95
94
  form.append("user", JSON.stringify(payload.user));
96
95
  }
@@ -172,29 +171,26 @@ function createApiClient(options) {
172
171
  return "healthy";
173
172
  }
174
173
  async function listChangelog(externalId) {
175
- const response = await fetcher(
176
- `${endpoint}/api/feedback/v1/reports/widget/changelog/`,
177
- { method: "GET", headers: widgetHeaders(externalId) }
178
- );
174
+ const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/changelog/`, {
175
+ method: "GET",
176
+ headers: widgetHeaders(externalId)
177
+ });
179
178
  if (response.status === 404) return [];
180
179
  return readJsonArray(response, "listChangelog");
181
180
  }
182
181
  async function getReport(reportId, externalId, opts) {
183
- const response = await fetcher(
184
- `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
185
- {
186
- method: "GET",
187
- headers: widgetHeaders(externalId),
188
- ...opts?.signal ? { signal: opts.signal } : {}
189
- }
190
- );
182
+ const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
183
+ method: "GET",
184
+ headers: widgetHeaders(externalId),
185
+ ...opts?.signal ? { signal: opts.signal } : {}
186
+ });
191
187
  return readJsonObject(response, "getReport");
192
188
  }
193
189
  async function getReportBySeq(seq, externalId) {
194
- const response = await fetcher(
195
- `${endpoint}/api/feedback/v1/reports/widget/by-seq/${seq}/`,
196
- { method: "GET", headers: widgetHeaders(externalId) }
197
- );
190
+ const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/by-seq/${seq}/`, {
191
+ method: "GET",
192
+ headers: widgetHeaders(externalId)
193
+ });
198
194
  return readJsonObject(response, "getReportBySeq");
199
195
  }
200
196
  async function addComment(reportId, externalId, body, clientNonce, attachments) {
@@ -203,9 +199,7 @@ function createApiClient(options) {
203
199
  const form = new FormData();
204
200
  form.append("body", body);
205
201
  if (clientNonce !== void 0) form.append("client_nonce", clientNonce);
206
- attachments.forEach(
207
- (blob, i) => form.append("attachment", blob, `attachment-${i + 1}.png`)
208
- );
202
+ attachments.forEach((blob, i) => form.append("attachment", blob, `attachment-${i + 1}.png`));
209
203
  init = { method: "POST", headers: widgetHeaders(externalId), body: form };
210
204
  } else {
211
205
  init = {
@@ -231,17 +225,14 @@ function createApiClient(options) {
231
225
  return response.json();
232
226
  }
233
227
  async function editDescription(reportId, externalId, description) {
234
- const response = await fetcher(
235
- `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
236
- {
237
- method: "PATCH",
238
- headers: {
239
- ...widgetHeaders(externalId),
240
- "Content-Type": "application/json"
241
- },
242
- body: JSON.stringify({ description })
243
- }
244
- );
228
+ const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
229
+ method: "PATCH",
230
+ headers: {
231
+ ...widgetHeaders(externalId),
232
+ "Content-Type": "application/json"
233
+ },
234
+ body: JSON.stringify({ description })
235
+ });
245
236
  if (!response.ok) {
246
237
  const text = await response.text().catch(() => "");
247
238
  throw new Error(`editDescription failed: ${response.status} ${text}`);
@@ -249,17 +240,14 @@ function createApiClient(options) {
249
240
  return response.json();
250
241
  }
251
242
  async function closeAsResolved(reportId, externalId) {
252
- const response = await fetcher(
253
- `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
254
- {
255
- method: "PATCH",
256
- headers: {
257
- ...widgetHeaders(externalId),
258
- "Content-Type": "application/json"
259
- },
260
- body: JSON.stringify({ status: "closed" })
261
- }
262
- );
243
+ const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
244
+ method: "PATCH",
245
+ headers: {
246
+ ...widgetHeaders(externalId),
247
+ "Content-Type": "application/json"
248
+ },
249
+ body: JSON.stringify({ status: "closed" })
250
+ });
263
251
  if (!response.ok) {
264
252
  const text = await response.text().catch(() => "");
265
253
  throw new Error(`closeAsResolved failed: ${response.status} ${text}`);
@@ -267,17 +255,14 @@ function createApiClient(options) {
267
255
  return response.json();
268
256
  }
269
257
  async function reopenUnresolved(reportId, externalId) {
270
- const response = await fetcher(
271
- `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
272
- {
273
- method: "PATCH",
274
- headers: {
275
- ...widgetHeaders(externalId),
276
- "Content-Type": "application/json"
277
- },
278
- body: JSON.stringify({ status: "in_progress" })
279
- }
280
- );
258
+ const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
259
+ method: "PATCH",
260
+ headers: {
261
+ ...widgetHeaders(externalId),
262
+ "Content-Type": "application/json"
263
+ },
264
+ body: JSON.stringify({ status: "in_progress" })
265
+ });
281
266
  if (!response.ok) {
282
267
  const text = await response.text().catch(() => "");
283
268
  throw new Error(`reopenUnresolved failed: ${response.status} ${text}`);
@@ -331,20 +316,17 @@ function createApiClient(options) {
331
316
  return readJsonObject(response, "fetchBoardKpis");
332
317
  }
333
318
  async function checkVisibility(externalId, email) {
334
- const response = await fetcher(
335
- `${endpoint}/api/feedback/v1/widget/visibility/`,
336
- {
337
- method: "POST",
338
- headers: {
339
- ...widgetHeaders(externalId),
340
- "Content-Type": "application/json"
341
- },
342
- body: JSON.stringify({
343
- external_id: externalId,
344
- ...email ? { email } : {}
345
- })
346
- }
347
- );
319
+ const response = await fetcher(`${endpoint}/api/feedback/v1/widget/visibility/`, {
320
+ method: "POST",
321
+ headers: {
322
+ ...widgetHeaders(externalId),
323
+ "Content-Type": "application/json"
324
+ },
325
+ body: JSON.stringify({
326
+ external_id: externalId,
327
+ ...email ? { email } : {}
328
+ })
329
+ });
348
330
  if (!response.ok) {
349
331
  throw new Error(`checkVisibility failed: ${response.status}`);
350
332
  }
@@ -677,6 +659,9 @@ var DEFAULT_STRINGS = {
677
659
  "form.cancel": "Cancel",
678
660
  "form.close": "Close",
679
661
  "form.submitting": "Sending\u2026",
662
+ "form.meta.action": "Report a problem with this feedback tool",
663
+ "form.meta.badge": "\u2192 goes to the feedback-tool team",
664
+ "form.meta.cancel": "Back to normal feedback",
680
665
  "form.success": "Thanks \u2014 your feedback was sent.",
681
666
  "form.success.seq": "Thanks \u2014 report #{seq} was sent. \u2713",
682
667
  "form.discard.title": "Unsaved changes",
@@ -850,6 +835,10 @@ var DEFAULT_STRINGS = {
850
835
  "detail.tech.errors": "Runtime errors",
851
836
  "detail.tech.console": "Console (last 20)",
852
837
  "detail.tech.network": "Network (last 15)",
838
+ "detail.verified.badge": "Agent-verified",
839
+ "detail.verified.title": "Proof of fix",
840
+ "detail.verified.functional": "Functional check",
841
+ "detail.verified.ui": "UI check",
853
842
  "status.new": "New",
854
843
  "status.in_progress": "In progress",
855
844
  "status.awaiting_validation": "Awaiting your validation",
@@ -877,6 +866,9 @@ var FRENCH_STRINGS = {
877
866
  "form.cancel": "Annuler",
878
867
  "form.close": "Fermer",
879
868
  "form.submitting": "Envoi\u2026",
869
+ "form.meta.action": "Signaler un probl\xE8me avec l'outil de feedback",
870
+ "form.meta.badge": "\u2192 envoy\xE9 \xE0 l'\xE9quipe de l'outil de feedback",
871
+ "form.meta.cancel": "Revenir au commentaire normal",
880
872
  "form.success": "Merci \u2014 votre commentaire a \xE9t\xE9 envoy\xE9.",
881
873
  "form.success.seq": "Merci \u2014 rapport n\xB0{seq} envoy\xE9. \u2713",
882
874
  "form.discard.title": "Modifications non enregistr\xE9es",
@@ -1050,6 +1042,10 @@ var FRENCH_STRINGS = {
1050
1042
  "detail.tech.errors": "Erreurs d\u2019ex\xE9cution",
1051
1043
  "detail.tech.console": "Console (20 derniers)",
1052
1044
  "detail.tech.network": "R\xE9seau (15 derniers)",
1045
+ "detail.verified.badge": "V\xE9rifi\xE9 par l\u2019agent",
1046
+ "detail.verified.title": "Preuve de correction",
1047
+ "detail.verified.functional": "Fonctionnel v\xE9rifi\xE9",
1048
+ "detail.verified.ui": "Interface v\xE9rifi\xE9e",
1053
1049
  "status.new": "Nouveau",
1054
1050
  "status.in_progress": "En cours",
1055
1051
  "status.awaiting_validation": "En attente de validation",
@@ -1310,6 +1306,26 @@ var ALLOWED_IMAGE_TYPES = [
1310
1306
  ];
1311
1307
  var MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
1312
1308
  var MAX_SCREENSHOTS = 5;
1309
+ var ALLOWED_ATTACHMENT_TYPES = [
1310
+ ...ALLOWED_IMAGE_TYPES,
1311
+ "application/pdf",
1312
+ "text/plain",
1313
+ "text/csv",
1314
+ "application/json",
1315
+ ""
1316
+ ];
1317
+ var ATTACHMENT_ACCEPT = "image/png,image/jpeg,image/webp,application/pdf,text/plain,.log,.txt,.csv,.json";
1318
+ function validateAttachmentFile(file) {
1319
+ if (!ALLOWED_ATTACHMENT_TYPES.includes(
1320
+ file.type
1321
+ )) {
1322
+ return { kind: "type" };
1323
+ }
1324
+ if (file.size > MAX_SCREENSHOT_BYTES) {
1325
+ return { kind: "size", maxMb: MAX_SCREENSHOT_BYTES / (1024 * 1024) };
1326
+ }
1327
+ return null;
1328
+ }
1313
1329
  function validateScreenshotFile(file) {
1314
1330
  if (!ALLOWED_IMAGE_TYPES.includes(
1315
1331
  file.type
@@ -1396,7 +1412,7 @@ function ReportDetailView({
1396
1412
  const remaining = MAX_SCREENSHOTS - composeShots.length;
1397
1413
  const batch = files.slice(0, Math.max(0, remaining));
1398
1414
  for (const file of batch) {
1399
- const err = validateScreenshotFile(file);
1415
+ const err = validateAttachmentFile(file);
1400
1416
  if (err) {
1401
1417
  setAttachError(
1402
1418
  err.kind === "type" ? strings["form.screenshot.error_type"] : strings["form.screenshot.error_size"].replace("{max}", String(err.maxMb))
@@ -1621,7 +1637,10 @@ function ReportDetailView({
1621
1637
  "\u2190 ",
1622
1638
  strings["detail.back"]
1623
1639
  ] }),
1624
- /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
1640
+ /* @__PURE__ */ jsxs3("span", { class: "report-detail-header-pills", children: [
1641
+ /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status }),
1642
+ detail.verification && /* @__PURE__ */ jsx3("span", { class: "pill pill-verified", children: strings["detail.verified.badge"] })
1643
+ ] })
1625
1644
  ] }),
1626
1645
  variant === "board" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header report-detail-header--board", children: [
1627
1646
  /* @__PURE__ */ jsxs3(
@@ -1637,7 +1656,10 @@ function ReportDetailView({
1637
1656
  ]
1638
1657
  }
1639
1658
  ),
1640
- /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
1659
+ /* @__PURE__ */ jsxs3("span", { class: "report-detail-header-pills", children: [
1660
+ /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status }),
1661
+ detail.verification && /* @__PURE__ */ jsx3("span", { class: "pill pill-verified", children: strings["detail.verified.badge"] })
1662
+ ] })
1641
1663
  ] }),
1642
1664
  /* @__PURE__ */ jsxs3("div", { class: "report-detail-body", children: [
1643
1665
  /* @__PURE__ */ jsx3(ContextBlock, { detail, strings }),
@@ -1711,6 +1733,15 @@ function ReportDetailView({
1711
1733
  }
1712
1734
  ) : /* @__PURE__ */ jsx3("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx3("img", { src: url, alt: "", loading: "lazy", onError: () => unpinUrl(pinKey) }) });
1713
1735
  }),
1736
+ detail.verification && /* @__PURE__ */ jsx3(
1737
+ VerificationPanel,
1738
+ {
1739
+ verification: detail.verification,
1740
+ strings,
1741
+ pinUrl,
1742
+ unpinUrl
1743
+ }
1744
+ ),
1714
1745
  /* @__PURE__ */ jsx3("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
1715
1746
  detail.comments.length === 0 ? /* @__PURE__ */ jsx3("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx3("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(
1716
1747
  CommentBubble,
@@ -1753,7 +1784,7 @@ function ReportDetailView({
1753
1784
  {
1754
1785
  ref: fileInputRef,
1755
1786
  type: "file",
1756
- accept: "image/png,image/jpeg,image/webp",
1787
+ accept: ATTACHMENT_ACCEPT,
1757
1788
  multiple: true,
1758
1789
  class: "compose-attach-input",
1759
1790
  onChange: handleAttachChange,
@@ -1879,6 +1910,68 @@ function TechnicalContextDrawer({ ctx, strings }) {
1879
1910
  ] })
1880
1911
  ] });
1881
1912
  }
1913
+ function VerificationPanel({
1914
+ verification,
1915
+ strings,
1916
+ pinUrl,
1917
+ unpinUrl
1918
+ }) {
1919
+ return /* @__PURE__ */ jsxs3("details", { class: "report-detail-verification", children: [
1920
+ /* @__PURE__ */ jsx3("summary", { children: strings["detail.verified.title"] }),
1921
+ /* @__PURE__ */ jsxs3("div", { class: "verification-body", children: [
1922
+ /* @__PURE__ */ jsx3("p", { class: "verification-evidence", children: verification.evidence }),
1923
+ /* @__PURE__ */ jsxs3("div", { class: "verification-meta", children: [
1924
+ /* @__PURE__ */ jsxs3("span", { children: [
1925
+ verification.functional_ok ? "\u2713" : "\u2717",
1926
+ " ",
1927
+ strings["detail.verified.functional"]
1928
+ ] }),
1929
+ /* @__PURE__ */ jsxs3("span", { children: [
1930
+ verification.ui_ok ? "\u2713" : "\u2717",
1931
+ " ",
1932
+ strings["detail.verified.ui"]
1933
+ ] }),
1934
+ /* @__PURE__ */ jsxs3("span", { children: [
1935
+ verification.environment,
1936
+ verification.app_version ? ` \xB7 ${verification.app_version}` : ""
1937
+ ] })
1938
+ ] }),
1939
+ verification.screenshots.length > 0 && /* @__PURE__ */ jsx3("div", { class: "verification-shots", children: verification.screenshots.map((s, i) => {
1940
+ const pinKey = `verif:${i}`;
1941
+ const url = pinUrl(pinKey, s.url);
1942
+ if (!url) return null;
1943
+ const safeHref = safeExternalHref(url);
1944
+ return safeHref ? /* @__PURE__ */ jsx3(
1945
+ "a",
1946
+ {
1947
+ class: "report-detail-screenshot",
1948
+ href: safeHref,
1949
+ target: "_blank",
1950
+ rel: "noopener noreferrer",
1951
+ children: /* @__PURE__ */ jsx3(
1952
+ "img",
1953
+ {
1954
+ src: url,
1955
+ alt: s.caption ?? "",
1956
+ loading: "lazy",
1957
+ onError: () => unpinUrl(pinKey)
1958
+ }
1959
+ )
1960
+ },
1961
+ i
1962
+ ) : /* @__PURE__ */ jsx3("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx3(
1963
+ "img",
1964
+ {
1965
+ src: url,
1966
+ alt: s.caption ?? "",
1967
+ loading: "lazy",
1968
+ onError: () => unpinUrl(pinKey)
1969
+ }
1970
+ ) }, i);
1971
+ }) })
1972
+ ] })
1973
+ ] });
1974
+ }
1882
1975
  function DeviceSection({
1883
1976
  device,
1884
1977
  strings
@@ -3301,8 +3394,10 @@ function Form({
3301
3394
  status,
3302
3395
  errorMessage,
3303
3396
  submittedSeq,
3304
- onDirtyChange
3397
+ onDirtyChange,
3398
+ metaFeedbackEnabled
3305
3399
  }) {
3400
+ const [metaMode, setMetaMode] = useState6(false);
3306
3401
  const [description, setDescription] = useState6("");
3307
3402
  const [feedbackType, setFeedbackType] = useState6("bug");
3308
3403
  const [severity, setSeverity] = useState6("medium");
@@ -3422,10 +3517,14 @@ function Form({
3422
3517
  values.screenshots = shots.map((s) => s.blob);
3423
3518
  values.capture_method = "manual";
3424
3519
  }
3520
+ if (metaMode) {
3521
+ values.meta = true;
3522
+ }
3425
3523
  onSubmit(values);
3426
3524
  };
3427
3525
  return /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, onPaste: handlePaste, children: [
3428
3526
  /* @__PURE__ */ jsx9("h2", { children: strings["form.title"] }),
3527
+ metaMode && /* @__PURE__ */ jsx9("div", { class: "meta-badge", children: strings["form.meta.badge"] }),
3429
3528
  /* @__PURE__ */ jsxs9("div", { class: "field", children: [
3430
3529
  /* @__PURE__ */ jsx9("label", { for: "mfb-desc", children: strings["form.description.label"] }),
3431
3530
  /* @__PURE__ */ jsx9(
@@ -3592,6 +3691,16 @@ function Form({
3592
3691
  /* @__PURE__ */ jsx9("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
3593
3692
  /* @__PURE__ */ jsx9("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
3594
3693
  ] }),
3694
+ metaFeedbackEnabled && /* @__PURE__ */ jsx9(
3695
+ "button",
3696
+ {
3697
+ type: "button",
3698
+ class: "meta-toggle",
3699
+ onClick: () => setMetaMode(!metaMode),
3700
+ disabled: submitting,
3701
+ children: metaMode ? strings["form.meta.cancel"] : strings["form.meta.action"]
3702
+ }
3703
+ ),
3595
3704
  annotatingIndex !== null && shots[annotatingIndex] && /* @__PURE__ */ jsx9(
3596
3705
  Annotator,
3597
3706
  {
@@ -4455,6 +4564,12 @@ var WIDGET_STYLES = `
4455
4564
  /* REQ-A7: quiet transparency line under the form. */
4456
4565
  .capture-notice { color: var(--mfb-muted, #6b7280); font-size: 11px; line-height: 1.4; margin-top: 6px; }
4457
4566
 
4567
+ /* Meta-feedback: footer escape hatch + active-mode badge. Deliberately as
4568
+ quiet as the capture notice \u2014 never competes with the host's own flow. */
4569
+ .meta-toggle { background: none; border: none; padding: 0; margin-top: 10px; color: var(--mfb-muted, #6b7280); font-size: 11px; text-decoration: underline; cursor: pointer; align-self: flex-start; }
4570
+ .meta-toggle:hover { color: var(--mfb-text, #111827); }
4571
+ .meta-badge { display: inline-block; margin: 2px 0 6px; padding: 2px 8px; border-radius: 999px; background: var(--mfb-accent-soft, #eef2ff); color: var(--mfb-accent, #4f46e5); font-size: 11px; font-weight: 600; }
4572
+
4458
4573
  /* #85: inline "#NN" report reference rendered as a link (a button for a11y). */
4459
4574
  .seq-link {
4460
4575
  display: inline;
@@ -4965,6 +5080,10 @@ var WIDGET_STYLES = `
4965
5080
  .pill-status--awaiting_validation { background: #faf5ff; color: #6b21a8; border-color: #e9d5ff; }
4966
5081
  .pill-status--closed { background: #ecfdf5; color: #065f46; border-color: #a7f3d0; }
4967
5082
  .pill-status--rejected, .pill-status--wontfix, .pill-status--duplicate { background: #f3f4f6; color: #374151; border-color: #e5e7eb; }
5083
+ /* Agent-verified fix badge (Phase B) \u2014 emerald, same family as
5084
+ .pill-status--closed but its own modifier since it layers alongside
5085
+ the status pill rather than replacing it. */
5086
+ .pill-verified { background: #ecfdf5; color: #047857; border-color: #a7f3d0; }
4968
5087
  .pill-type { background: var(--mfb-surface); color: var(--mfb-text-muted); border-color: var(--mfb-border); }
4969
5088
  .pill-severity { background: var(--mfb-surface); color: var(--mfb-text-muted); border-color: var(--mfb-border); }
4970
5089
  .pill-severity--blocker { background: #fef2f2; color: #991b1b; border-color: #fecaca; }
@@ -4979,6 +5098,7 @@ var WIDGET_STYLES = `
4979
5098
  justify-content: space-between;
4980
5099
  gap: 8px;
4981
5100
  }
5101
+ .report-detail-header-pills { display: inline-flex; align-items: center; gap: 6px; }
4982
5102
  .report-detail-body { display: flex; flex-direction: column; gap: 10px; }
4983
5103
  .report-detail-description {
4984
5104
  font-size: 14px;
@@ -5332,12 +5452,14 @@ button.report-detail-edit {
5332
5452
  /* Transparency drawer \u2014 closed by default. The user can click open to
5333
5453
  verify what their browser sent: device, errors, console tail, network
5334
5454
  tail. Read-only; purely a trust-building gesture. */
5335
- .report-detail-tech {
5455
+ .report-detail-tech,
5456
+ .report-detail-verification {
5336
5457
  border: 1px solid var(--mfb-border);
5337
5458
  border-radius: var(--mfb-radius);
5338
5459
  background: var(--mfb-surface);
5339
5460
  }
5340
- .report-detail-tech > summary {
5461
+ .report-detail-tech > summary,
5462
+ .report-detail-verification > summary {
5341
5463
  cursor: pointer;
5342
5464
  padding: 8px 10px;
5343
5465
  font-size: 11px;
@@ -5347,15 +5469,19 @@ button.report-detail-edit {
5347
5469
  user-select: none;
5348
5470
  list-style: none;
5349
5471
  }
5350
- .report-detail-tech > summary::-webkit-details-marker { display: none; }
5351
- .report-detail-tech > summary::before {
5472
+ .report-detail-tech > summary::-webkit-details-marker,
5473
+ .report-detail-verification > summary::-webkit-details-marker { display: none; }
5474
+ .report-detail-tech > summary::before,
5475
+ .report-detail-verification > summary::before {
5352
5476
  content: '\u25B8';
5353
5477
  display: inline-block;
5354
5478
  margin-right: 6px;
5355
5479
  transition: transform 120ms;
5356
5480
  }
5357
- .report-detail-tech[open] > summary::before { transform: rotate(90deg); }
5358
- .tech-body {
5481
+ .report-detail-tech[open] > summary::before,
5482
+ .report-detail-verification[open] > summary::before { transform: rotate(90deg); }
5483
+ .tech-body,
5484
+ .verification-body {
5359
5485
  padding: 0 10px 10px 10px;
5360
5486
  display: flex;
5361
5487
  flex-direction: column;
@@ -5435,6 +5561,31 @@ button.report-detail-edit {
5435
5561
  .tech-network-url { word-break: break-all; }
5436
5562
  .tech-network-time { text-align: right; color: var(--mfb-text-muted); font-variant-numeric: tabular-nums; }
5437
5563
 
5564
+ /* Proof-of-fix drawer (Phase B) \u2014 shares the collapsible shell rules
5565
+ with .report-detail-tech above (grouped selectors): closed by
5566
+ default, rotating \u25B8 marker. Surfaces an agent's verification pass
5567
+ so the submitter can see the evidence behind the "V\xE9rifi\xE9 par
5568
+ l'agent" badge. */
5569
+ .verification-evidence {
5570
+ margin: 0;
5571
+ font-size: 12px;
5572
+ white-space: pre-wrap;
5573
+ }
5574
+ .verification-meta {
5575
+ display: flex;
5576
+ gap: 12px;
5577
+ flex-wrap: wrap;
5578
+ font-size: 11px;
5579
+ color: var(--mfb-text-muted);
5580
+ }
5581
+ .verification-shots {
5582
+ display: flex;
5583
+ gap: 8px;
5584
+ flex-wrap: wrap;
5585
+ }
5586
+ .verification-shots .report-detail-screenshot { width: 96px; flex: 0 0 auto; }
5587
+ .verification-shots .report-detail-screenshot img { max-height: 96px; }
5588
+
5438
5589
  /* KPI strip \u2014 four pills above the My Reports list. Each is clickable
5439
5590
  to filter the rows below. Borrowed from thePnr's FeedbackKPICards. */
5440
5591
  .kpi-strip {
@@ -6202,8 +6353,7 @@ function mountWidget(options) {
6202
6353
  });
6203
6354
  }, 1200);
6204
6355
  } catch (err) {
6205
- if (typeof console !== "undefined")
6206
- console.warn("[mhosaic] submit:", err);
6356
+ if (typeof console !== "undefined") console.warn("[mhosaic] submit:", err);
6207
6357
  rerender({
6208
6358
  ...currentState,
6209
6359
  status: "error",
@@ -6237,9 +6387,7 @@ function mountWidget(options) {
6237
6387
  enabled: pageActivityEnabled
6238
6388
  });
6239
6389
  const fabLabel = pageSignal.open === 1 ? options.strings["page.badge.aria.one"] : pageSignal.open > 0 ? options.strings["page.badge.aria"].replace("{n}", String(pageSignal.open)) : options.strings["fab.label"];
6240
- const [visibilityAllowed, setVisibilityAllowed] = useState10(
6241
- !options.requiresVisibilityCheck
6242
- );
6390
+ const [visibilityAllowed, setVisibilityAllowed] = useState10(!options.requiresVisibilityCheck);
6243
6391
  useEffect10(() => {
6244
6392
  if (!options.requiresVisibilityCheck) {
6245
6393
  setVisibilityAllowed(true);
@@ -6443,6 +6591,9 @@ function mountWidget(options) {
6443
6591
  },
6444
6592
  onDirtyChange: (d) => {
6445
6593
  formDirty = d;
6594
+ },
6595
+ ...options.metaFeedbackEnabled !== void 0 && {
6596
+ metaFeedbackEnabled: options.metaFeedbackEnabled
6446
6597
  }
6447
6598
  }
6448
6599
  )
@@ -6486,8 +6637,12 @@ function mountWidget(options) {
6486
6637
  api: options.api,
6487
6638
  externalId,
6488
6639
  strings: options.strings,
6489
- ...options.getCurrentPage !== void 0 && { getCurrentPage: options.getCurrentPage },
6490
- ...state.boardSelectId !== void 0 && { initialSelectedId: state.boardSelectId }
6640
+ ...options.getCurrentPage !== void 0 && {
6641
+ getCurrentPage: options.getCurrentPage
6642
+ },
6643
+ ...state.boardSelectId !== void 0 && {
6644
+ initialSelectedId: state.boardSelectId
6645
+ }
6491
6646
  }
6492
6647
  )
6493
6648
  ]
@@ -6502,9 +6657,7 @@ function mountWidget(options) {
6502
6657
  if (!api || !externalId || !ref) return;
6503
6658
  const seq = /^\d+$/.test(ref) ? parseInt(ref, 10) : null;
6504
6659
  const resolve = seq !== null ? api.getReportBySeq(seq, externalId) : api.getReport(ref, externalId);
6505
- void resolve.then(
6506
- (r) => rerender({ ...currentState, open: true, tab: "mine", selectedReportId: r.id })
6507
- ).catch(() => {
6660
+ void resolve.then((r) => rerender({ ...currentState, open: true, tab: "mine", selectedReportId: r.id })).catch(() => {
6508
6661
  });
6509
6662
  }
6510
6663
  if (options.deepLinkParam !== false) {
@@ -6543,10 +6696,7 @@ function defaultQaPosition() {
6543
6696
  function createFeedback(config) {
6544
6697
  const env = config.env ?? "prod";
6545
6698
  const locale = config.locale ?? (typeof navigator !== "undefined" ? navigator.language : void 0);
6546
- const strings = resolveStrings(
6547
- config.translations ?? {},
6548
- locale !== void 0 ? { locale } : {}
6549
- );
6699
+ const strings = resolveStrings(config.translations ?? {}, locale !== void 0 ? { locale } : {});
6550
6700
  const capture = installCapture({
6551
6701
  ...config.sanitizeUrl !== void 0 && { sanitizeUrl: config.sanitizeUrl }
6552
6702
  });
@@ -6601,14 +6751,15 @@ function createFeedback(config) {
6601
6751
  capture_method: captureMethod,
6602
6752
  technical_context
6603
6753
  };
6604
- if ("0.35.0") {
6605
- payload.widget_version = "0.35.0";
6754
+ if ("0.37.0") {
6755
+ payload.widget_version = "0.37.0";
6606
6756
  }
6607
6757
  if (manualScreenshots?.length) {
6608
6758
  payload.screenshots = manualScreenshots;
6609
6759
  payload.screenshot = manualScreenshots[0];
6610
6760
  }
6611
6761
  if (values.synthetic) payload.synthetic = true;
6762
+ if (values.meta) payload.meta = true;
6612
6763
  const pagePath = currentPagePath(config);
6613
6764
  if (pagePath) payload.page_path = pagePath;
6614
6765
  if (user?.id !== void 0 && user.id !== null && user.id !== "") {
@@ -6658,12 +6809,15 @@ function createFeedback(config) {
6658
6809
  ...config.openToCurrentPageFeedback !== void 0 && {
6659
6810
  openToCurrentPageFeedback: config.openToCurrentPageFeedback
6660
6811
  },
6812
+ ...config.metaFeedbackEnabled !== void 0 && {
6813
+ metaFeedbackEnabled: config.metaFeedbackEnabled
6814
+ },
6661
6815
  ...config.showPageActivity !== void 0 && {
6662
6816
  showPageActivity: config.showPageActivity
6663
6817
  }
6664
6818
  };
6665
6819
  const handle = mountWidget(mountOptions);
6666
- if (config.openToCurrentPageFeedback === void 0 && config.showFAB !== false) {
6820
+ if ((config.openToCurrentPageFeedback === void 0 || config.metaFeedbackEnabled === void 0) && config.showFAB !== false) {
6667
6821
  const behaviorFetch = config.fetchImpl ?? globalThis.fetch;
6668
6822
  void (async () => {
6669
6823
  try {
@@ -6676,6 +6830,10 @@ function createFeedback(config) {
6676
6830
  if (typeof v === "boolean" && mountOptions.openToCurrentPageFeedback === void 0) {
6677
6831
  mountOptions.openToCurrentPageFeedback = v;
6678
6832
  }
6833
+ const meta = m?.config?.behavior?.metaFeedbackEnabled;
6834
+ if (typeof meta === "boolean" && mountOptions.metaFeedbackEnabled === void 0) {
6835
+ mountOptions.metaFeedbackEnabled = meta;
6836
+ }
6679
6837
  } catch {
6680
6838
  }
6681
6839
  })();
@@ -6746,4 +6904,4 @@ function createFeedback(config) {
6746
6904
  export {
6747
6905
  createFeedback
6748
6906
  };
6749
- //# sourceMappingURL=chunk-A2TMODNI.mjs.map
6907
+ //# sourceMappingURL=chunk-C3ILJCVI.mjs.map